diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 4075baeeede..5a07494745e 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -55,9 +55,31 @@ jobs: go-version-file: go.mod cache: true + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: "24" + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@10.28.1 --activate + + - name: Generate nodejs + run: make ci-node-generate + + - name: Generate go + run: make ci-go-generate + + - name: Vulnerability scan + run: make govulncheck + + - name: Lint + run: make ci-golangci-lint + - name: Build ocis run: make -C ocis build + - name: Build debug binary + run: make -C ocis build-debug + - name: Unit tests run: make test diff --git a/Makefile b/Makefile index 79dc6dbd907..0da1c120f54 100644 --- a/Makefile +++ b/Makefile @@ -333,7 +333,7 @@ changelog-csv: $(CALENS) .PHONY: govulncheck govulncheck: $(GOVULNCHECK) - $(GOVULNCHECK) ./... + scripts/govulncheck-wrapper.sh $(GOVULNCHECK) .PHONY: l10n-push l10n-push: diff --git a/go.mod b/go.mod index c1b7de742bb..c8d9a1af7ac 100644 --- a/go.mod +++ b/go.mod @@ -55,8 +55,8 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mna/pigeon v1.3.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/nats-io/nats-server/v2 v2.12.4 - github.com/nats-io/nats.go v1.48.0 + github.com/nats-io/nats-server/v2 v2.12.6 + github.com/nats-io/nats.go v1.49.0 github.com/olekukonko/tablewriter v1.1.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo/v2 v2.28.1 @@ -94,16 +94,16 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 go.opentelemetry.io/otel/sdk v1.40.0 go.opentelemetry.io/otel/trace v1.40.0 - golang.org/x/crypto v0.48.0 + golang.org/x/crypto v0.49.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b - golang.org/x/image v0.36.0 - golang.org/x/net v0.49.0 + golang.org/x/image v0.38.0 + golang.org/x/net v0.51.0 golang.org/x/oauth2 v0.35.0 - golang.org/x/sync v0.19.0 - golang.org/x/term v0.40.0 - golang.org/x/text v0.34.0 + golang.org/x/sync v0.20.0 + golang.org/x/term v0.41.0 + golang.org/x/text v0.35.0 google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 - google.golang.org/grpc v1.79.1 + google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 gotest.tools/v3 v3.5.2 @@ -125,7 +125,7 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/alexedwards/argon2id v1.0.0 // indirect github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect - github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op // indirect + github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.8 // indirect @@ -155,7 +155,7 @@ require ( github.com/ceph/go-ceph v0.38.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/cornelk/hashmap v1.0.8 // indirect @@ -237,7 +237,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/juliangruber/go-intersect v1.1.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.2.11 // indirect github.com/klauspost/crc32 v1.3.0 // indirect github.com/kovidgoyal/go-parallel v1.1.1 // indirect @@ -273,8 +273,8 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/jwt/v2 v2.8.0 // indirect - github.com/nats-io/nkeys v0.4.12 // indirect + github.com/nats-io/jwt/v2 v2.8.1 // indirect + github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.2.0 // indirect @@ -298,7 +298,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/russellhaering/goxmldsig v1.5.0 // indirect + github.com/russellhaering/goxmldsig v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/segmentio/asm v1.2.1 // indirect @@ -340,10 +340,10 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 7fd0d5eb664..8a49b5f8913 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM= -github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op h1:kpBdlEPbRvff0mDD1gk7o9BhI16b9p5yYAXRlidpqJE= +github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= @@ -182,8 +182,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= @@ -575,8 +575,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -701,14 +701,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= -github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts= -github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg= -github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= -github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= -github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= -github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg= +github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU= +github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg= +github.com/nats-io/nats-server/v2 v2.12.6 h1:Egbx9Vl7Ch8wTtpXPGqbehkZ+IncKqShUxvrt1+Enc8= +github.com/nats-io/nats-server/v2 v2.12.6/go.mod h1:4HPlrvtmSO3yd7KcElDNMx9kv5EBJBnJJzQPptXlheo= +github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE= +github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw= +github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= +github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -827,8 +827,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw= -github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk= +github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks= +github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= @@ -1022,8 +1022,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1039,8 +1039,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAf golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= -golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1065,8 +1065,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1112,8 +1112,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1140,8 +1140,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1207,8 +1207,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1219,8 +1219,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1234,13 +1234,13 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1290,8 +1290,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1371,8 +1371,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/scripts/govulncheck-wrapper.sh b/scripts/govulncheck-wrapper.sh new file mode 100755 index 00000000000..6c57ddea9cd --- /dev/null +++ b/scripts/govulncheck-wrapper.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# Wrapper around govulncheck that only fails on fixable, called vulnerabilities. +# +# - CALLED + fixable (non-stdlib) → FAIL (you should bump the dep) +# - CALLED + stdlib fix only → WARN (needs Go toolchain upgrade) +# - CALLED + no fix available → WARN (nothing to do yet) +# - imported/required only → WARN (code doesn't call it) +# +# Usage: scripts/govulncheck-wrapper.sh [govulncheck-binary] +# If no binary is provided, uses 'govulncheck' from PATH. + +set -euo pipefail + +GOVULNCHECK="${1:-govulncheck}" +TMPFILE=$(mktemp) +trap 'rm -f "$TMPFILE"' EXIT + +echo "Running govulncheck..." +"$GOVULNCHECK" -format json ./... > "$TMPFILE" 2>&1 || true + +python3 - "$TMPFILE" <<'PYEOF' +import json +import sys + +def parse_json_stream(path): + with open(path) as f: + content = f.read() + decoder = json.JSONDecoder() + idx = 0 + objects = [] + while idx < len(content): + while idx < len(content) and content[idx] in ' \t\n\r': + idx += 1 + if idx >= len(content): + break + obj, end_idx = decoder.raw_decode(content, idx) + idx = end_idx + objects.append(obj) + return objects + +objects = parse_json_stream(sys.argv[1]) + +# Collect OSV details +osvs = {} +for obj in objects: + if 'osv' in obj: + osv = obj['osv'] + osvs[osv['id']] = osv + +# Collect findings +findings = [obj['finding'] for obj in objects if 'finding' in obj] + +# Group by vuln ID +from collections import defaultdict +by_vuln = defaultdict(list) +for f in findings: + by_vuln[f['osv']].append(f) + +fail_vulns = [] +warn_vulns = [] + +for vid, entries in sorted(by_vuln.items()): + # Check if any trace reaches symbol level (has 'function' in trace frames) + is_called = any( + any('function' in frame for frame in entry.get('trace', [])) + for entry in entries + ) + fixed_version = entries[0].get('fixed_version', '') + trace = entries[0].get('trace', []) + module = trace[0].get('module', '') if trace else '' + + # Determine category + if not is_called: + category = "IMPORTED" + elif not fixed_version: + category = "NO_FIX" + elif module == "stdlib": + category = "STDLIB" + else: + category = "FIXABLE" + + osv = osvs.get(vid, {}) + summary = osv.get('summary', vid) + + info = { + 'id': vid, + 'category': category, + 'module': module, + 'fixed_version': fixed_version, + 'summary': summary, + } + + if category == "FIXABLE": + fail_vulns.append(info) + else: + warn_vulns.append(info) + +# Print warnings +if warn_vulns: + print("\n⚠ Vulnerabilities acknowledged (not blocking):") + for v in warn_vulns: + reason = { + 'NO_FIX': 'no upstream fix available', + 'STDLIB': f'needs Go toolchain upgrade to {v["fixed_version"]}', + 'IMPORTED': 'code does not call vulnerable function', + }.get(v['category'], v['category']) + print(f" {v['id']}: {v['summary']}") + print(f" module={v['module']} ({reason})") + +# Print failures +if fail_vulns: + print(f"\n✗ {len(fail_vulns)} fixable vulnerability(ies) found:") + for v in fail_vulns: + print(f" {v['id']}: {v['summary']}") + print(f" module={v['module']}, fix: bump to {v['fixed_version']}") + sys.exit(1) +else: + print(f"\n✓ No fixable vulnerabilities found ({len(warn_vulns)} acknowledged warnings)") + sys.exit(0) +PYEOF diff --git a/tests/acceptance/features/cliCommands/uploadSessions.feature b/tests/acceptance/features/cliCommands/uploadSessions.feature index e3ad26dbdf5..c9ef2f1ba19 100644 --- a/tests/acceptance/features/cliCommands/uploadSessions.feature +++ b/tests/acceptance/features/cliCommands/uploadSessions.feature @@ -59,8 +59,8 @@ Feature: List upload sessions via CLI command Scenario: list and cleanup the expired upload sessions Given a file "large.zip" with the size of "2GB" has been created locally - And the config "STORAGE_USERS_UPLOAD_EXPIRATION" has been set to "1" for "storageuser" service And user "Alice" has uploaded a file from "filesForUpload/textfile.txt" to "file.txt" via TUS inside of the space "Personal" using the WebDAV API + And the config "STORAGE_USERS_UPLOAD_EXPIRATION" has been set to "1" for "storageuser" service And user "Alice" has tried to upload file "filesForUpload/large.zip" to "large.zip" inside space "Personal" via TUS When the administrator lists all the upload sessions with flag "expired" Then the command should be successful @@ -203,8 +203,8 @@ Feature: List upload sessions via CLI command Scenario: restart expired upload sessions Given a file "large.zip" with the size of "2GB" has been created locally - And the config "STORAGE_USERS_UPLOAD_EXPIRATION" has been set to "1" for "storageuser" service And user "Alice" has uploaded a file from "filesForUpload/textfile.txt" to "file.txt" via TUS inside of the space "Personal" using the WebDAV API + And the config "STORAGE_USERS_UPLOAD_EXPIRATION" has been set to "1" for "storageuser" service And user "Alice" has tried to upload file "filesForUpload/large.zip" to "large.zip" inside space "Personal" via TUS When the administrator restarts the expired upload sessions using the CLI Then the command should be successful diff --git a/vendor/github.com/antithesishq/antithesis-sdk-go/internal/sdk_const.go b/vendor/github.com/antithesishq/antithesis-sdk-go/internal/sdk_const.go index a4db4e7e1ef..5eccf1b58c0 100644 --- a/vendor/github.com/antithesishq/antithesis-sdk-go/internal/sdk_const.go +++ b/vendor/github.com/antithesishq/antithesis-sdk-go/internal/sdk_const.go @@ -3,7 +3,7 @@ package internal // -------------------------------------------------------------------------------- // Versions // -------------------------------------------------------------------------------- -const SDK_Version = "0.5.0" +const SDK_Version = "0.6.0" const Protocol_Version = "1.1.0" // -------------------------------------------------------------------------------- diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go index 052fc8d32d2..0910613465a 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go @@ -14,14 +14,14 @@ import "unsafe" type storageBuf [maxRate / 8]uint64 func (b *storageBuf) asBytes() *[maxRate]byte { - return (*[maxRate]byte)(unsafe.Pointer(b)) + return (*[maxRate]byte)(unsafe.Pointer(b)) //nolint:gosec } // xorInuses unaligned reads and writes to update d.a to contain d.a // XOR buf. func xorIn(d *State, buf []byte) { n := len(buf) - bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] + bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] //nolint:gosec if n >= 72 { d.a[0] ^= bw[0] d.a[1] ^= bw[1] @@ -56,6 +56,6 @@ func xorIn(d *State, buf []byte) { } func copyOut(d *State, buf []byte) { - ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) + ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) //nolint:gosec copy(buf, ab[:]) } diff --git a/vendor/github.com/cloudflare/circl/sign/sign.go b/vendor/github.com/cloudflare/circl/sign/sign.go index 557d6f09605..1247f1b626e 100644 --- a/vendor/github.com/cloudflare/circl/sign/sign.go +++ b/vendor/github.com/cloudflare/circl/sign/sign.go @@ -38,6 +38,12 @@ type PrivateKey interface { encoding.BinaryMarshaler } +// A private key that retains the seed with which it was generated. +type Seeded interface { + // returns the seed if retained, otherwise nil + Seed() []byte +} + // A Scheme represents a specific instance of a signature scheme. type Scheme interface { // Name of the scheme. diff --git a/vendor/github.com/klauspost/compress/README.md b/vendor/github.com/klauspost/compress/README.md index af2ef639536..5125c1f267e 100644 --- a/vendor/github.com/klauspost/compress/README.md +++ b/vendor/github.com/klauspost/compress/README.md @@ -7,7 +7,7 @@ This package provides various compression algorithms. * Optimized [deflate](https://godoc.org/github.com/klauspost/compress/flate) packages which can be used as a dropin replacement for [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip) and [zlib](https://godoc.org/github.com/klauspost/compress/zlib). * [snappy](https://github.com/klauspost/compress/tree/master/snappy) is a drop-in replacement for `github.com/golang/snappy` offering better compression and concurrent streams. * [huff0](https://github.com/klauspost/compress/tree/master/huff0) and [FSE](https://github.com/klauspost/compress/tree/master/fse) implementations for raw entropy encoding. -* [gzhttp](https://github.com/klauspost/compress/tree/master/gzhttp) Provides client and server wrappers for handling gzipped requests efficiently. +* [gzhttp](https://github.com/klauspost/compress/tree/master/gzhttp) Provides client and server wrappers for handling gzipped/zstd HTTP requests efficiently. * [pgzip](https://github.com/klauspost/pgzip) is a separate package that provides a very fast parallel gzip implementation. [![Go Reference](https://pkg.go.dev/badge/klauspost/compress.svg)](https://pkg.go.dev/github.com/klauspost/compress?tab=subdirectories) @@ -26,8 +26,14 @@ This package will support the current Go version and 2 versions back. Use the links above for more information on each. # changelog +* Jan 16th, 2026 [1.18.3](https://github.com/klauspost/compress/releases/tag/v1.18.3) + * Downstream CVE-2025-61728. See [golang/go#77102](https://github.com/golang/go/issues/77102). -* Oct 20, 2025 - [1.18.1](https://github.com/klauspost/compress/releases/tag/v1.18.1) +* Dec 1st, 2025 - [1.18.2](https://github.com/klauspost/compress/releases/tag/v1.18.2) + * flate: Fix invalid encoding on level 9 with single value input in https://github.com/klauspost/compress/pull/1115 + * flate: reduce stateless allocations by @RXamzin in https://github.com/klauspost/compress/pull/1106 + +* Oct 20, 2025 - [1.18.1](https://github.com/klauspost/compress/releases/tag/v1.18.1) - RETRACTED * zstd: Add simple zstd EncodeTo/DecodeTo functions https://github.com/klauspost/compress/pull/1079 * zstd: Fix incorrect buffer size in dictionary encodes https://github.com/klauspost/compress/pull/1059 * s2: check for cap, not len of buffer in EncodeBetter/Best by @vdarulis in https://github.com/klauspost/compress/pull/1080 @@ -603,7 +609,7 @@ While the release has been extensively tested, it is recommended to testing when # deflate usage -The packages are drop-in replacements for standard libraries. Simply replace the import path to use them: +The packages are drop-in replacements for standard library [deflate](https://godoc.org/github.com/klauspost/compress/flate), [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip), and [zlib](https://godoc.org/github.com/klauspost/compress/zlib). Simply replace the import path to use them: Typical speed is about 2x of the standard library packages. @@ -614,17 +620,15 @@ Typical speed is about 2x of the standard library packages. | `archive/zip` | `github.com/klauspost/compress/zip` | [zip](https://pkg.go.dev/github.com/klauspost/compress/zip?tab=doc) | | `compress/flate` | `github.com/klauspost/compress/flate` | [flate](https://pkg.go.dev/github.com/klauspost/compress/flate?tab=doc) | -* Optimized [deflate](https://godoc.org/github.com/klauspost/compress/flate) packages which can be used as a dropin replacement for [gzip](https://godoc.org/github.com/klauspost/compress/gzip), [zip](https://godoc.org/github.com/klauspost/compress/zip) and [zlib](https://godoc.org/github.com/klauspost/compress/zlib). - -You may also be interested in [pgzip](https://github.com/klauspost/pgzip), which is a drop in replacement for gzip, which support multithreaded compression on big files and the optimized [crc32](https://github.com/klauspost/crc32) package used by these packages. +You may also be interested in [pgzip](https://github.com/klauspost/pgzip), which is a drop-in replacement for gzip, which support multithreaded compression on big files and the optimized [crc32](https://github.com/klauspost/crc32) package used by these packages. -The packages contains the same as the standard library, so you can use the godoc for that: [gzip](http://golang.org/pkg/compress/gzip/), [zip](http://golang.org/pkg/archive/zip/), [zlib](http://golang.org/pkg/compress/zlib/), [flate](http://golang.org/pkg/compress/flate/). +The packages implement the same API as the standard library, so you can use the original godoc documentation: [gzip](http://golang.org/pkg/compress/gzip/), [zip](http://golang.org/pkg/archive/zip/), [zlib](http://golang.org/pkg/compress/zlib/), [flate](http://golang.org/pkg/compress/flate/). Currently there is only minor speedup on decompression (mostly CRC32 calculation). Memory usage is typically 1MB for a Writer. stdlib is in the same range. If you expect to have a lot of concurrently allocated Writers consider using -the stateless compress described below. +the stateless compression described below. For compression performance, see: [this spreadsheet](https://docs.google.com/spreadsheets/d/1nuNE2nPfuINCZJRMt6wFWhKpToF95I47XjSsc-1rbPQ/edit?usp=sharing). @@ -684,3 +688,6 @@ Here are other packages of good quality and pure Go (no cgo wrappers or autoconv This code is licensed under the same conditions as the original Go code. See LICENSE file. + + + diff --git a/vendor/github.com/klauspost/compress/zstd/decoder.go b/vendor/github.com/klauspost/compress/zstd/decoder.go index 30df5513d56..c7e500f02a9 100644 --- a/vendor/github.com/klauspost/compress/zstd/decoder.go +++ b/vendor/github.com/klauspost/compress/zstd/decoder.go @@ -39,9 +39,6 @@ type Decoder struct { frame *frameDec - // Custom dictionaries. - dicts map[uint32]*dict - // streamWg is the waitgroup for all streams streamWg sync.WaitGroup } @@ -101,12 +98,10 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) { d.current.err = ErrDecoderNilInput } - // Transfer option dicts. - d.dicts = make(map[uint32]*dict, len(d.o.dicts)) - for _, dc := range d.o.dicts { - d.dicts[dc.id] = dc + // Initialize dict map if needed. + if d.o.dicts == nil { + d.o.dicts = make(map[uint32]*dict) } - d.o.dicts = nil // Create decoders d.decoders = make(chan *blockDec, d.o.concurrent) @@ -238,6 +233,21 @@ func (d *Decoder) Reset(r io.Reader) error { return nil } +// ResetWithOptions will reset the decoder and apply the given options +// for the next stream or DecodeAll operation. +// Options are applied on top of the existing options. +// Some options cannot be changed on reset and will return an error. +func (d *Decoder) ResetWithOptions(r io.Reader, opts ...DOption) error { + d.o.resetOpt = true + defer func() { d.o.resetOpt = false }() + for _, o := range opts { + if err := o(&d.o); err != nil { + return err + } + } + return d.Reset(r) +} + // drainOutput will drain the output until errEndOfStream is sent. func (d *Decoder) drainOutput() { if d.current.cancel != nil { @@ -930,7 +940,7 @@ decodeStream: } func (d *Decoder) setDict(frame *frameDec) (err error) { - dict, ok := d.dicts[frame.DictionaryID] + dict, ok := d.o.dicts[frame.DictionaryID] if ok { if debugDecoder { println("setting dict", frame.DictionaryID) diff --git a/vendor/github.com/klauspost/compress/zstd/decoder_options.go b/vendor/github.com/klauspost/compress/zstd/decoder_options.go index 774c5f00fe4..537627a0789 100644 --- a/vendor/github.com/klauspost/compress/zstd/decoder_options.go +++ b/vendor/github.com/klauspost/compress/zstd/decoder_options.go @@ -20,10 +20,11 @@ type decoderOptions struct { concurrent int maxDecodedSize uint64 maxWindowSize uint64 - dicts []*dict + dicts map[uint32]*dict ignoreChecksum bool limitToCap bool decodeBufsBelow int + resetOpt bool } func (o *decoderOptions) setDefault() { @@ -42,8 +43,15 @@ func (o *decoderOptions) setDefault() { // WithDecoderLowmem will set whether to use a lower amount of memory, // but possibly have to allocate more while running. +// Cannot be changed with ResetWithOptions. func WithDecoderLowmem(b bool) DOption { - return func(o *decoderOptions) error { o.lowMem = b; return nil } + return func(o *decoderOptions) error { + if o.resetOpt && b != o.lowMem { + return errors.New("WithDecoderLowmem cannot be changed on Reset") + } + o.lowMem = b + return nil + } } // WithDecoderConcurrency sets the number of created decoders. @@ -53,18 +61,23 @@ func WithDecoderLowmem(b bool) DOption { // inflight blocks. // When decoding streams and setting maximum to 1, // no async decoding will be done. +// The value supplied must be at least 0. // When a value of 0 is provided GOMAXPROCS will be used. // By default this will be set to 4 or GOMAXPROCS, whatever is lower. +// Cannot be changed with ResetWithOptions. func WithDecoderConcurrency(n int) DOption { return func(o *decoderOptions) error { if n < 0 { - return errors.New("concurrency must be at least 1") + return errors.New("concurrency must be at least 0") } + newVal := n if n == 0 { - o.concurrent = runtime.GOMAXPROCS(0) - } else { - o.concurrent = n + newVal = runtime.GOMAXPROCS(0) } + if o.resetOpt && newVal != o.concurrent { + return errors.New("WithDecoderConcurrency cannot be changed on Reset") + } + o.concurrent = newVal return nil } } @@ -73,6 +86,7 @@ func WithDecoderConcurrency(n int) DOption { // non-streaming operations or maximum window size for streaming operations. // This can be used to control memory usage of potentially hostile content. // Maximum is 1 << 63 bytes. Default is 64GiB. +// Can be changed with ResetWithOptions. func WithDecoderMaxMemory(n uint64) DOption { return func(o *decoderOptions) error { if n == 0 { @@ -92,16 +106,20 @@ func WithDecoderMaxMemory(n uint64) DOption { // "zstd --train" from the Zstandard reference implementation. // // If several dictionaries with the same ID are provided, the last one will be used. +// Can be changed with ResetWithOptions. // // [dictionary format]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary-format func WithDecoderDicts(dicts ...[]byte) DOption { return func(o *decoderOptions) error { + if o.dicts == nil { + o.dicts = make(map[uint32]*dict) + } for _, b := range dicts { d, err := loadDict(b) if err != nil { return err } - o.dicts = append(o.dicts, d) + o.dicts[d.id] = d } return nil } @@ -109,12 +127,16 @@ func WithDecoderDicts(dicts ...[]byte) DOption { // WithDecoderDictRaw registers a dictionary that may be used by the decoder. // The slice content can be arbitrary data. +// Can be changed with ResetWithOptions. func WithDecoderDictRaw(id uint32, content []byte) DOption { return func(o *decoderOptions) error { if bits.UintSize > 32 && uint(len(content)) > dictMaxLength { return fmt.Errorf("dictionary of size %d > 2GiB too large", len(content)) } - o.dicts = append(o.dicts, &dict{id: id, content: content, offsets: [3]int{1, 4, 8}}) + if o.dicts == nil { + o.dicts = make(map[uint32]*dict) + } + o.dicts[id] = &dict{id: id, content: content, offsets: [3]int{1, 4, 8}} return nil } } @@ -124,6 +146,7 @@ func WithDecoderDictRaw(id uint32, content []byte) DOption { // The Decoder will likely allocate more memory based on the WithDecoderLowmem setting. // If WithDecoderMaxMemory is set to a lower value, that will be used. // Default is 512MB, Maximum is ~3.75 TB as per zstandard spec. +// Can be changed with ResetWithOptions. func WithDecoderMaxWindow(size uint64) DOption { return func(o *decoderOptions) error { if size < MinWindowSize { @@ -141,6 +164,7 @@ func WithDecoderMaxWindow(size uint64) DOption { // or any size set in WithDecoderMaxMemory. // This can be used to limit decoding to a specific maximum output size. // Disabled by default. +// Can be changed with ResetWithOptions. func WithDecodeAllCapLimit(b bool) DOption { return func(o *decoderOptions) error { o.limitToCap = b @@ -153,17 +177,37 @@ func WithDecodeAllCapLimit(b bool) DOption { // This typically uses less allocations but will have the full decompressed object in memory. // Note that DecodeAllCapLimit will disable this, as well as giving a size of 0 or less. // Default is 128KiB. +// Cannot be changed with ResetWithOptions. func WithDecodeBuffersBelow(size int) DOption { return func(o *decoderOptions) error { + if o.resetOpt && size != o.decodeBufsBelow { + return errors.New("WithDecodeBuffersBelow cannot be changed on Reset") + } o.decodeBufsBelow = size return nil } } // IgnoreChecksum allows to forcibly ignore checksum checking. +// Can be changed with ResetWithOptions. func IgnoreChecksum(b bool) DOption { return func(o *decoderOptions) error { o.ignoreChecksum = b return nil } } + +// WithDecoderDictDelete removes dictionaries by ID. +// If no ids are passed, all dictionaries are deleted. +// Should be used with ResetWithOptions. +func WithDecoderDictDelete(ids ...uint32) DOption { + return func(o *decoderOptions) error { + if len(ids) == 0 { + clear(o.dicts) + } + for _, id := range ids { + delete(o.dicts, id) + } + return nil + } +} diff --git a/vendor/github.com/klauspost/compress/zstd/encoder.go b/vendor/github.com/klauspost/compress/zstd/encoder.go index 8f8223cd3a6..19e730acc26 100644 --- a/vendor/github.com/klauspost/compress/zstd/encoder.go +++ b/vendor/github.com/klauspost/compress/zstd/encoder.go @@ -131,6 +131,22 @@ func (e *Encoder) Reset(w io.Writer) { s.frameContentSize = 0 } +// ResetWithOptions will re-initialize the writer and apply the given options +// as a new, independent stream. +// Options are applied on top of the existing options. +// Some options cannot be changed on reset and will return an error. +func (e *Encoder) ResetWithOptions(w io.Writer, opts ...EOption) error { + e.o.resetOpt = true + defer func() { e.o.resetOpt = false }() + for _, o := range opts { + if err := o(&e.o); err != nil { + return err + } + } + e.Reset(w) + return nil +} + // ResetContentSize will reset and set a content size for the next stream. // If the bytes written does not match the size given an error will be returned // when calling Close(). diff --git a/vendor/github.com/klauspost/compress/zstd/encoder_options.go b/vendor/github.com/klauspost/compress/zstd/encoder_options.go index 20671dcb91d..8e0f5cac71b 100644 --- a/vendor/github.com/klauspost/compress/zstd/encoder_options.go +++ b/vendor/github.com/klauspost/compress/zstd/encoder_options.go @@ -14,6 +14,7 @@ type EOption func(*encoderOptions) error // options retains accumulated state of multiple options. type encoderOptions struct { + resetOpt bool concurrent int level EncoderLevel single *bool @@ -71,19 +72,28 @@ func (o encoderOptions) encoder() encoder { // WithEncoderCRC will add CRC value to output. // Output will be 4 bytes larger. +// Can be changed with ResetWithOptions. func WithEncoderCRC(b bool) EOption { return func(o *encoderOptions) error { o.crc = b; return nil } } // WithEncoderConcurrency will set the concurrency, // meaning the maximum number of encoders to run concurrently. -// The value supplied must be at least 1. +// The value supplied must be at least 0. +// When a value of 0 is provided GOMAXPROCS will be used. // For streams, setting a value of 1 will disable async compression. // By default this will be set to GOMAXPROCS. +// Cannot be changed with ResetWithOptions. func WithEncoderConcurrency(n int) EOption { return func(o *encoderOptions) error { - if n <= 0 { - return fmt.Errorf("concurrency must be at least 1") + if n < 0 { + return errors.New("concurrency must at least 0") + } + if n == 0 { + n = runtime.GOMAXPROCS(0) + } + if o.resetOpt && n != o.concurrent { + return errors.New("WithEncoderConcurrency cannot be changed on Reset") } o.concurrent = n return nil @@ -95,6 +105,7 @@ func WithEncoderConcurrency(n int) EOption { // A larger value will enable better compression but allocate more memory and, // for above-default values, take considerably longer. // The default value is determined by the compression level and max 8MB. +// Cannot be changed with ResetWithOptions. func WithWindowSize(n int) EOption { return func(o *encoderOptions) error { switch { @@ -105,6 +116,9 @@ func WithWindowSize(n int) EOption { case (n & (n - 1)) != 0: return errors.New("window size must be a power of 2") } + if o.resetOpt && n != o.windowSize { + return errors.New("WithWindowSize cannot be changed on Reset") + } o.windowSize = n o.customWindow = true @@ -122,6 +136,7 @@ func WithWindowSize(n int) EOption { // n must be > 0 and <= 1GB, 1<<30 bytes. // The padded area will be filled with data from crypto/rand.Reader. // If `EncodeAll` is used with data already in the destination, the total size will be multiple of this. +// Can be changed with ResetWithOptions. func WithEncoderPadding(n int) EOption { return func(o *encoderOptions) error { if n <= 0 { @@ -215,12 +230,16 @@ func (e EncoderLevel) String() string { } // WithEncoderLevel specifies a predefined compression level. +// Cannot be changed with ResetWithOptions. func WithEncoderLevel(l EncoderLevel) EOption { return func(o *encoderOptions) error { switch { case l <= speedNotSet || l >= speedLast: return fmt.Errorf("unknown encoder level") } + if o.resetOpt && l != o.level { + return errors.New("WithEncoderLevel cannot be changed on Reset") + } o.level = l if !o.customWindow { switch o.level { @@ -248,6 +267,7 @@ func WithEncoderLevel(l EncoderLevel) EOption { // WithZeroFrames will encode 0 length input as full frames. // This can be needed for compatibility with zstandard usage, // but is not needed for this package. +// Can be changed with ResetWithOptions. func WithZeroFrames(b bool) EOption { return func(o *encoderOptions) error { o.fullZero = b @@ -259,6 +279,7 @@ func WithZeroFrames(b bool) EOption { // Disabling this will skip incompressible data faster, but in cases with no matches but // skewed character distribution compression is lost. // Default value depends on the compression level selected. +// Can be changed with ResetWithOptions. func WithAllLitEntropyCompression(b bool) EOption { return func(o *encoderOptions) error { o.customALEntropy = true @@ -270,6 +291,7 @@ func WithAllLitEntropyCompression(b bool) EOption { // WithNoEntropyCompression will always skip entropy compression of literals. // This can be useful if content has matches, but unlikely to benefit from entropy // compression. Usually the slight speed improvement is not worth enabling this. +// Can be changed with ResetWithOptions. func WithNoEntropyCompression(b bool) EOption { return func(o *encoderOptions) error { o.noEntropy = b @@ -287,6 +309,7 @@ func WithNoEntropyCompression(b bool) EOption { // This is only a recommendation, each decoder is free to support higher or lower limits, depending on local limitations. // If this is not specified, block encodes will automatically choose this based on the input size and the window size. // This setting has no effect on streamed encodes. +// Can be changed with ResetWithOptions. func WithSingleSegment(b bool) EOption { return func(o *encoderOptions) error { o.single = &b @@ -298,8 +321,12 @@ func WithSingleSegment(b bool) EOption { // slower encoding speed. // This will not change the window size which is the primary function for reducing // memory usage. See WithWindowSize. +// Cannot be changed with ResetWithOptions. func WithLowerEncoderMem(b bool) EOption { return func(o *encoderOptions) error { + if o.resetOpt && b != o.lowMem { + return errors.New("WithLowerEncoderMem cannot be changed on Reset") + } o.lowMem = b return nil } @@ -311,6 +338,7 @@ func WithLowerEncoderMem(b bool) EOption { // "zstd --train" from the Zstandard reference implementation. // // The encoder *may* choose to use no dictionary instead for certain payloads. +// Can be changed with ResetWithOptions. // // [dictionary format]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary-format func WithEncoderDict(dict []byte) EOption { @@ -328,6 +356,7 @@ func WithEncoderDict(dict []byte) EOption { // // The slice content may contain arbitrary data. It will be used as an initial // history. +// Can be changed with ResetWithOptions. func WithEncoderDictRaw(id uint32, content []byte) EOption { return func(o *encoderOptions) error { if bits.UintSize > 32 && uint(len(content)) > dictMaxLength { @@ -337,3 +366,12 @@ func WithEncoderDictRaw(id uint32, content []byte) EOption { return nil } } + +// WithEncoderDictDelete clears the dictionary, so no dictionary will be used. +// Should be used with ResetWithOptions. +func WithEncoderDictDelete() EOption { + return func(o *encoderOptions) error { + o.dict = nil + return nil + } +} diff --git a/vendor/github.com/nats-io/jwt/v2/account_claims.go b/vendor/github.com/nats-io/jwt/v2/account_claims.go index 9da374aed04..d143bee5d73 100644 --- a/vendor/github.com/nats-io/jwt/v2/account_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go @@ -152,19 +152,22 @@ type Mapping map[Subject][]WeightedMapping func (m *Mapping) Validate(vr *ValidationResults) { for ubFrom, wm := range (map[Subject][]WeightedMapping)(*m) { ubFrom.Validate(vr) - perCluster := make(map[string]uint8) - total := uint8(0) + perCluster := make(map[string]uint32) + total := uint32(0) for _, e := range wm { e.Subject.Validate(vr) + if e.GetWeight() > 100 { + vr.AddError("Mapping %q has a weight %d that exceeds 100", ubFrom, e.GetWeight()) + } if e.Cluster != "" { t := perCluster[e.Cluster] - t += e.Weight + t += uint32(e.GetWeight()) perCluster[e.Cluster] = t if t > 100 { vr.AddError("Mapping %q in cluster %q exceeds 100%% among all of it's weighted to mappings", ubFrom, e.Cluster) } } else { - total += e.GetWeight() + total += uint32(e.GetWeight()) } } if total > 100 { @@ -286,7 +289,7 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { tvr := CreateValidationResults() a.Trace.Destination.Validate(tvr) if !tvr.IsEmpty() { - vr.AddError(fmt.Sprintf("the account Trace.Destination %s", tvr.Issues[0].Description)) + vr.AddError("the account Trace.Destination %s", tvr.Issues[0].Description) } if a.Trace.Destination.HasWildCards() { vr.AddError("the account Trace.Destination subject %q is not a valid publish subject", a.Trace.Destination) @@ -325,7 +328,7 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { a.Info.Validate(vr) if err := a.ClusterTraffic.Valid(); err != nil { - vr.AddError(err.Error()) + vr.AddError("%s", err.Error()) } } diff --git a/vendor/github.com/nats-io/jwt/v2/creds_utils.go b/vendor/github.com/nats-io/jwt/v2/creds_utils.go index eade49d63fa..726b3299b9a 100644 --- a/vendor/github.com/nats-io/jwt/v2/creds_utils.go +++ b/vendor/github.com/nats-io/jwt/v2/creds_utils.go @@ -63,6 +63,9 @@ func formatJwt(kind string, jwtString string) ([]byte, error) { func DecorateSeed(seed []byte) ([]byte, error) { w := bytes.NewBuffer(nil) ts := bytes.TrimSpace(seed) + if len(ts) < 2 { + return nil, errors.New("seed is too short") + } pre := string(ts[0:2]) kind := "" switch pre { @@ -138,6 +141,18 @@ func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) { return nil, fmt.Errorf("nkey seed is not an user seed") } + kp, err := nkeys.FromSeed(seed) + if err != nil { + return nil, err + } + pk, err := kp.PublicKey() + if err != nil { + return nil, err + } + if pk != gc.Claims().Subject { + return nil, fmt.Errorf("nkey seed does not match the jwt subject") + } + d, err := DecorateSeed(seed) if err != nil { return nil, err diff --git a/vendor/github.com/nats-io/jwt/v2/decoder.go b/vendor/github.com/nats-io/jwt/v2/decoder.go index 1e043a384d3..3a10ee2157e 100644 --- a/vendor/github.com/nats-io/jwt/v2/decoder.go +++ b/vendor/github.com/nats-io/jwt/v2/decoder.go @@ -26,6 +26,12 @@ import ( const libVersion = 2 +// MaxTokenSize is the maximum size of a JWT token in bytes +const MaxTokenSize = 1024 * 1024 // 1MB + +// ErrTokenTooLarge is returned when a token exceeds MaxTokenSize +var ErrTokenTooLarge = errors.New("token too large") + type identifier struct { Type ClaimType `json:"type,omitempty"` GenericFields `json:"nats,omitempty"` @@ -56,6 +62,9 @@ type v1ClaimsDataDeletedFields struct { // doesn't match the expected algorithm, or the claim is // not valid or verification fails an error is returned. func Decode(token string) (Claims, error) { + if len(token) > MaxTokenSize { + return nil, fmt.Errorf("token size %d exceeds maximum of %d bytes: %w", len(token), MaxTokenSize, ErrTokenTooLarge) + } // must have 3 chunks chunks := strings.Split(token, ".") if len(chunks) != 3 { diff --git a/vendor/github.com/nats-io/jwt/v2/imports.go b/vendor/github.com/nats-io/jwt/v2/imports.go index c8524d07795..1fe52cdf0bb 100644 --- a/vendor/github.com/nats-io/jwt/v2/imports.go +++ b/vendor/github.com/nats-io/jwt/v2/imports.go @@ -126,7 +126,8 @@ type Imports []*Import // Validate checks if an import is valid for the wrapping account func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) { - toSet := make(map[Subject]struct{}, len(*i)) + // Group subjects by account to check for overlaps only within the same account + subsByAcct := make(map[string]map[Subject]struct{}, len(*i)) for _, v := range *i { if v == nil { vr.AddError("null import is not allowed") @@ -140,15 +141,19 @@ func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) { if sub == "" { sub = v.Subject } - for k := range toSet { - if sub.IsContainedIn(k) || k.IsContainedIn(sub) { - vr.AddError("overlapping subject namespace for %q and %q", sub, k) + // Check for overlapping subjects only within the same account + for subOther := range subsByAcct[v.Account] { + if sub.IsContainedIn(subOther) || subOther.IsContainedIn(sub) { + vr.AddError("overlapping subject namespace for %q and %q in same account %q", sub, subOther, v.Account) } } - if _, ok := toSet[sub]; ok { - vr.AddError("overlapping subject namespace for %q", v.To) + if subsByAcct[v.Account] == nil { + subsByAcct[v.Account] = make(map[Subject]struct{}, len(*i)) } - toSet[sub] = struct{}{} + if _, ok := subsByAcct[v.Account][sub]; ok { + vr.AddError("overlapping subject namespace for %q in account %q", sub, v.Account) + } + subsByAcct[v.Account][sub] = struct{}{} } v.Validate(acctPubKey, vr) } diff --git a/vendor/github.com/nats-io/jwt/v2/operator_claims.go b/vendor/github.com/nats-io/jwt/v2/operator_claims.go index b5c9c94c329..12432f65f88 100644 --- a/vendor/github.com/nats-io/jwt/v2/operator_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/operator_claims.go @@ -71,12 +71,12 @@ func ParseServerVersion(version string) (int, int, int, error) { // Validate checks the validity of the operators contents func (o *Operator) Validate(vr *ValidationResults) { if err := o.validateAccountServerURL(); err != nil { - vr.AddError(err.Error()) + vr.AddError("%s", err.Error()) } for _, v := range o.validateOperatorServiceURLs() { if v != nil { - vr.AddError(v.Error()) + vr.AddError("%s", v.Error()) } } diff --git a/vendor/github.com/nats-io/nats-server/v2/conf/parse.go b/vendor/github.com/nats-io/nats-server/v2/conf/parse.go index b94c539f064..8229e402a76 100644 --- a/vendor/github.com/nats-io/nats-server/v2/conf/parse.go +++ b/vendor/github.com/nats-io/nats-server/v2/conf/parse.go @@ -114,42 +114,28 @@ func ParseFileWithChecks(fp string) (map[string]any, error) { return p.mapping, nil } -// cleanupUsedEnvVars will recursively remove all already used -// environment variables which might be in the parsed tree. -func cleanupUsedEnvVars(m map[string]any) { - for k, v := range m { - t := v.(*token) - if t.usedVariable { - delete(m, k) - continue - } - // Cleanup any other env var that is still in the map. - if tm, ok := t.value.(map[string]any); ok { - cleanupUsedEnvVars(tm) - } +// configDigest returns a digest for the parsed config. +func configDigest(m map[string]any) (string, error) { + digest := sha256.New() + e := json.NewEncoder(digest) + if err := e.Encode(m); err != nil { + return _EMPTY_, err } + return fmt.Sprintf("sha256:%x", digest.Sum(nil)), nil } // ParseFileWithChecksDigest returns the processed config and a digest // that represents the configuration. func ParseFileWithChecksDigest(fp string) (map[string]any, string, error) { - data, err := os.ReadFile(fp) + m, err := ParseFileWithChecks(fp) if err != nil { return nil, _EMPTY_, err } - p, err := parse(string(data), fp, true) - if err != nil { - return nil, _EMPTY_, err - } - // Filter out any environment variables before taking the digest. - cleanupUsedEnvVars(p.mapping) - digest := sha256.New() - e := json.NewEncoder(digest) - err = e.Encode(p.mapping) + digest, err := configDigest(m) if err != nil { return nil, _EMPTY_, err } - return p.mapping, fmt.Sprintf("sha256:%x", digest.Sum(nil)), nil + return m, digest, nil } type token struct { diff --git a/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go b/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go index 878ffbca498..e155ad485d3 100644 --- a/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go +++ b/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go @@ -235,19 +235,19 @@ func (d *DN) RDNsMatch(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } - -CheckNextRDN: + matched := make([]bool, len(other.RDNs)) for _, irdn := range d.RDNs { - for _, ordn := range other.RDNs { - if (len(irdn.Attributes) == len(ordn.Attributes)) && - (irdn.hasAllAttributes(ordn.Attributes) && ordn.hasAllAttributes(irdn.Attributes)) { - // Found the RDN, check if next one matches. - continue CheckNextRDN + found := false + for j, ordn := range other.RDNs { + if !matched[j] && irdn.Equal(ordn) { + matched[j] = true + found = true + break } } - - // Could not find a matching individual RDN, auth fails. - return false + if !found { + return false + } } return true } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go index 44f52d3a7d5..225f92e726d 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go @@ -138,6 +138,12 @@ type sconns struct { leafs int32 } +// clampInt64ToInt32 safely converts an int64 limit to int32, +// clamping values to the [math.MinInt32, math.MaxInt32] range. +func clampInt64ToInt32(v int64) int32 { + return int32(max(math.MinInt32, min(math.MaxInt32, v))) +} + // Import stream mapping struct type streamImport struct { acc *Account @@ -1610,10 +1616,12 @@ func (a *Account) checkServiceImportsForCycles(from string, visited map[string]b } // Push ourselves and check si.acc visited[a.Name] = true - if subjectIsSubsetMatch(si.from, from) { - from = si.from + // Make a copy to not overwrite the passed value. + f := from + if subjectIsSubsetMatch(si.from, f) { + f = si.from } - if err := si.acc.checkServiceImportsForCycles(from, visited); err != nil { + if err := si.acc.checkServiceImportsForCycles(f, visited); err != nil { return err } a.mu.RLock() @@ -1668,10 +1676,12 @@ func (a *Account) checkStreamImportsForCycles(to string, visited map[string]bool } // Push ourselves and check si.acc visited[a.Name] = true - if subjectIsSubsetMatch(si.to, to) { - to = si.to + // Make a copy to not overwrite the passed value. + t := to + if subjectIsSubsetMatch(si.to, t) { + t = si.to } - if err := si.acc.checkStreamImportsForCycles(to, visited); err != nil { + if err := si.acc.checkStreamImportsForCycles(t, visited); err != nil { return err } a.mu.RLock() @@ -3716,10 +3726,10 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim // Now do limits if they are present. a.mu.Lock() - a.msubs = int32(ac.Limits.Subs) - a.mpay = int32(ac.Limits.Payload) - a.mconns = int32(ac.Limits.Conn) - a.mleafs = int32(ac.Limits.LeafNodeConn) + a.msubs = clampInt64ToInt32(ac.Limits.Subs) + a.mpay = clampInt64ToInt32(ac.Limits.Payload) + a.mconns = clampInt64ToInt32(ac.Limits.Conn) + a.mleafs = clampInt64ToInt32(ac.Limits.LeafNodeConn) a.disallowBearer = ac.Limits.DisallowBearer // Check for any revocations if len(ac.Revocations) > 0 { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/auth.go b/vendor/github.com/nats-io/nats-server/v2/server/auth.go index ede297441b4..b8cf69783ae 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/auth.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/auth.go @@ -421,7 +421,9 @@ func (c *client) matchesPinnedCert(tlsPinnedCerts PinnedCertSet) bool { } var ( - mustacheRE = regexp.MustCompile(`{{2}([^}]+)}{2}`) + mustacheRE = regexp.MustCompile(`{{2}([^}]+)}{2}`) + maxPermTemplateSubjectExpansions = 4096 + errPermTemplateExpansionLimit error = fmt.Errorf("template expansion exceeds limit") ) func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.UserClaims, acc *Account) (jwt.UserPermissionLimits, error) { @@ -456,11 +458,11 @@ func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.User return p } isTag := func(op string) []string { - if strings.EqualFold("tag(", op[:4]) && strings.HasSuffix(op, ")") { + if len(op) >= 4 && strings.EqualFold("tag(", op[:4]) && strings.HasSuffix(op, ")") { v := strings.TrimPrefix(op, "tag(") v = strings.TrimSuffix(v, ")") return []string{"tag", v} - } else if strings.EqualFold("account-tag(", op[:12]) && strings.HasSuffix(op, ")") { + } else if len(op) >= 12 && strings.EqualFold("account-tag(", op[:12]) && strings.HasSuffix(op, ")") { v := strings.TrimPrefix(op, "account-tag(") v = strings.TrimSuffix(v, ")") return []string{"account-tag", v} @@ -529,7 +531,7 @@ func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.User // generate an invalid subject? values[tokenNum] = []string{" "} } - } else if failOnBadSubject { + } else { return nil, fmt.Errorf("template operation in %q: %q is not defined", list[i], op) } } @@ -544,6 +546,20 @@ func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.User return nil, fmt.Errorf("generated invalid subject") } } else { + expCount := 1 + for _, v := range values { + if len(v) == 0 { + expCount = 0 + break + } + if expCount > maxPermTemplateSubjectExpansions/len(v) { + return nil, fmt.Errorf("%w: %d", errPermTemplateExpansionLimit, maxPermTemplateSubjectExpansions) + } + expCount *= len(v) + } + if len(emittedList) > maxPermTemplateSubjectExpansions-expCount { + return nil, fmt.Errorf("%w: %d", errPermTemplateExpansionLimit, maxPermTemplateSubjectExpansions) + } a := nArrayCartesianProduct(values...) for _, aa := range a { subj := list[i] @@ -588,6 +604,7 @@ func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.User func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (authorized bool) { var ( nkey *NkeyUser + ujwt string juc *jwt.UserClaims acc *Account user *User @@ -782,16 +799,23 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au // Check if we have trustedKeys defined in the server. If so we require a user jwt. if s.trustedKeys != nil { - if c.opts.JWT == _EMPTY_ && opts.DefaultSentinel != _EMPTY_ { + ujwt = c.opts.JWT + if ujwt == _EMPTY_ && c.isMqtt() { + // For MQTT, we pass the password as the JWT too, but do so here so it's not + // publicly exposed in the client options if it isn't a JWT. + ujwt = c.opts.Password + } + if ujwt == _EMPTY_ && opts.DefaultSentinel != _EMPTY_ { c.opts.JWT = opts.DefaultSentinel + ujwt = c.opts.JWT } - if c.opts.JWT == _EMPTY_ { + if ujwt == _EMPTY_ { s.mu.Unlock() c.Debugf("Authentication requires a user JWT") return false } // So we have a valid user jwt here. - juc, err = jwt.DecodeUserClaims(c.opts.JWT) + juc, err = jwt.DecodeUserClaims(ujwt) if err != nil { s.mu.Unlock() c.Debugf("User JWT not valid: %v", err) @@ -1061,6 +1085,11 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au // Hold onto the user's public key. c.mu.Lock() c.pubKey = juc.Subject + // If this is a MQTT client, we purposefully didn't populate the JWT as it could contain + // a password or token. Now we know it's a valid JWT, we can populate it. + if c.isMqtt() { + c.opts.JWT = ujwt + } c.tags = juc.Tags c.nameTag = juc.Name c.mu.Unlock() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go b/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go index a7ed073e932..df903b3f259 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go @@ -32,6 +32,14 @@ const ( AuthRequestXKeyHeader = "Nats-Server-Xkey" ) +func titleCase(m string) string { + r := []rune(m) + if len(r) == 0 { + return _EMPTY_ + } + return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...)) +} + // Process a callout on this client's behalf. func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequired, trustedProxy bool) (authorized bool, errStr string) { isOperatorMode := len(opts.TrustedKeys) > 0 @@ -50,6 +58,13 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequi } else { acc = c.acc } + if acc == nil { + // FIX for https://github.com/nats-io/nats-server/issues/7841 + // hand rolled creds on leafnode became crasher here + errStr = fmt.Sprintf("%s not mapped to a callout account", c.kindString()) + s.Warnf(errStr) + return false, errStr + } // Check if we have been requested to encrypt. var xkp nkeys.KeyPair @@ -66,9 +81,6 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequi xkp, xkey = s.xkp, s.info.XKey } - // FIXME: so things like the server ID that get assigned, are used as a sort of nonce - but - // reality is that the keypair here, is generated, so the response generated a JWT has to be - // this user - no replay possible // Create a keypair for the user. We will expect this public user to be in the signed response. // This prevents replay attacks. ukp, _ := nkeys.CreateUser() @@ -234,11 +246,6 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequi } processReply := func(_ *subscription, rc *client, racc *Account, subject, reply string, rmsg []byte) { - titleCase := func(m string) string { - r := []rune(m) - return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...)) - } - arc, err := decodeResponse(rc, rmsg, racc) if err != nil { c.authViolation() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/client.go b/vendor/github.com/nats-io/nats-server/v2/server/client.go index 0e4aa4f18c0..0abfd218006 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/client.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/client.go @@ -153,7 +153,6 @@ const ( compressionNegotiated // Marks if this connection has negotiated compression level with remote. didTLSFirst // Marks if this connection requested and was accepted doing the TLS handshake first (prior to INFO). isSlowConsumer // Marks connection as a slow consumer. - firstPong // Marks if this is the first PONG received ) // set the flag (would be equivalent to set the boolean to true) @@ -869,6 +868,11 @@ func (c *client) registerWithAccount(acc *Account) error { } c.mu.Lock() + // This check does not apply to SYSTEM or JETSTREAM or ACCOUNT clients (because they don't have a `nc`...) + if c.isClosed() && !isInternalClient(c.kind) { + c.mu.Unlock() + return ErrConnectionClosed + } kind := c.kind srv := c.srv c.acc = acc @@ -928,14 +932,14 @@ func (c *client) applyAccountLimits() { c.msubs = jwt.NoLimit if c.opts.JWT != _EMPTY_ { // user jwt implies account if uc, _ := jwt.DecodeUserClaims(c.opts.JWT); uc != nil { - atomic.StoreInt32(&c.mpay, int32(uc.Limits.Payload)) - c.msubs = int32(uc.Limits.Subs) + atomic.StoreInt32(&c.mpay, clampInt64ToInt32(uc.Limits.Payload)) + c.msubs = clampInt64ToInt32(uc.Limits.Subs) if uc.IssuerAccount != _EMPTY_ && uc.IssuerAccount != uc.Issuer { if scope, ok := c.acc.signingKeys[uc.Issuer]; ok { if userScope, ok := scope.(*jwt.UserScope); ok { // if signing key disappeared or changed and we don't get here, the client will be disconnected - c.mpay = int32(userScope.Template.Limits.Payload) - c.msubs = int32(userScope.Template.Limits.Subs) + c.mpay = clampInt64ToInt32(userScope.Template.Limits.Payload) + c.msubs = clampInt64ToInt32(userScope.Template.Limits.Subs) } } } @@ -1353,6 +1357,13 @@ func (c *client) flushClients(budget time.Duration) time.Time { return last } +func (c *client) resetReadLoopStallTime() { + if c.in.tst >= stallClientMaxDuration { + c.rateLimitFormatWarnf("Producer was stalled for a total of %v", c.in.tst.Round(time.Millisecond)) + } + c.in.tst = 0 +} + // readLoop is the main socket read functionality. // Runs in its own Go routine. func (c *client) readLoop(pre []byte) { @@ -1430,21 +1441,6 @@ func (c *client) readLoop(pre []byte) { return } } - if ws { - bufs, err = c.wsRead(wsr, reader, b[:n]) - if bufs == nil && err != nil { - if err != io.EOF { - c.Errorf("read error: %v", err) - } - c.closeConnection(closedStateForErr(err)) - return - } else if bufs == nil { - continue - } - } else { - bufs[0] = b[:n] - } - // Check if the account has mappings and if so set the local readcache flag. // We check here to make sure any changes such as config reload are reflected here. if c.kind == CLIENT || c.kind == LEAF { @@ -1462,17 +1458,32 @@ func (c *client) readLoop(pre []byte) { c.in.bytes = 0 c.in.subs = 0 + if ws { + err = c.wsReadAndParse(wsr, reader, b[:n]) + if err != nil { + // Match the normal parse path: any already-buffered deliveries + // need their pending flush signals drained before we close. + c.flushClients(0) + if err != io.EOF { + c.Errorf("read error: %v", err) + } + c.closeConnection(closedStateForErr(err)) + return + } + c.resetReadLoopStallTime() + goto postParse + } else { + bufs[0] = b[:n] + } + // Main call into parser for inbound data. This will generate callouts // to process messages, etc. for i := 0; i < len(bufs); i++ { if err := c.parse(bufs[i]); err != nil { if err == ErrMinimumVersionRequired { // Special case here, currently only for leaf node connections. - // When process the CONNECT protocol, if the minimum version - // required was not met, an error was printed and sent back to - // the remote, and connection was closed after a certain delay - // (to avoid "rapid" reconnection from the remote). - // We don't need to do any of the things below, simply return. + // processLeafConnect() already sent the rejection and closed + // the connection, so there is nothing else to do here. return } if dur := time.Since(c.in.start); dur >= readLoopReportThreshold { @@ -1489,13 +1500,10 @@ func (c *client) readLoop(pre []byte) { } return } - // Clear total stalled time here. - if c.in.tst >= stallClientMaxDuration { - c.rateLimitFormatWarnf("Producer was stalled for a total of %v", c.in.tst.Round(time.Millisecond)) - } - c.in.tst = 0 + c.resetReadLoopStallTime() } + postParse: // If we are a ROUTER/LEAF and have processed an INFO, it is possible that // we are asked to switch to compression now. if checkCompress && c.in.flags.isSet(switchToCompression) { @@ -1686,9 +1694,11 @@ func (c *client) flushOutbound() bool { cw.Reset(&bb) for _, buf := range collapsed { - if _, err = cw.Write(buf); err != nil { - break + if err == nil { + _, err = cw.Write(buf) } + // Return always after consumed or error. + nbPoolPut(buf) } if err == nil { err = cw.Close() @@ -2113,41 +2123,37 @@ func (c *client) processErr(errStr string) { } } -// Password pattern matcher. -var passPat = regexp.MustCompile(`"?\s*pass\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)`) -var tokenPat = regexp.MustCompile(`"?\s*auth_token\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)`) +// Matcher for pass/password and auth_token fields. +var prefixAuthPat = regexp.MustCompile(`"?\s*(?:auth_token\S*?|pass\S*?)"?\s*[:=]\s*"?([^",\r\n}]*)`) + +// Exact matcher for fields sig, proxy_sig and nkey. +// Overlapping field "sig" does not match inside "proxy_sig". +var exactAuthPat = regexp.MustCompile(`(?:^|[^A-Za-z0-9_])"?\s*(?:proxy_sig|nkey|sig)"?\s*[:=]\s*"?([^",\r\n}]*)`) // removeSecretsFromTrace removes any notion of passwords/tokens from trace // messages for logging. func removeSecretsFromTrace(arg []byte) []byte { - buf := redact("pass", passPat, arg) - return redact("auth_token", tokenPat, buf) + buf := redact(prefixAuthPat, arg) + return redact(exactAuthPat, buf) } -func redact(name string, pat *regexp.Regexp, proto []byte) []byte { - if !bytes.Contains(proto, []byte(name)) { +func redact(pat *regexp.Regexp, proto []byte) []byte { + m := pat.FindAllSubmatchIndex(proto, -1) + if len(m) == 0 { return proto } // Take a copy of the connect proto just for the trace message. var _arg [4096]byte buf := append(_arg[:0], proto...) - - m := pat.FindAllSubmatchIndex(buf, -1) - if len(m) == 0 { - return proto - } - redactedPass := []byte("[REDACTED]") - for _, i := range m { - if len(i) < 4 { + for i := len(m) - 1; i >= 0; i-- { + match := m[i] + if len(match) < 4 { continue } - start := i[2] - end := i[3] - + start, end := match[2], match[3] // Replace value substring. buf = append(buf[:start], append(redactedPass, buf[end:]...)...) - break } return buf } @@ -2683,11 +2689,9 @@ func (c *client) processPong() { c.rtt = computeRTT(c.rttStart) srv := c.srv reorderGWs := c.kind == GATEWAY && c.gw.outbound - firstPong := c.flags.setIfNotSet(firstPong) var ri *routeInfo - // When receiving the first PONG, for a route with pooling, we may be - // instructed to start a new route. - if firstPong && c.kind == ROUTER && c.route != nil { + // For a route with pooling, we may be instructed to start a new route. + if c.kind == ROUTER && c.route != nil && c.route.startNewRoute != nil { ri = c.route.startNewRoute c.route.startNewRoute = nil } @@ -2805,9 +2809,10 @@ func (c *client) processHeaderPub(arg, remaining []byte) error { // look for the tracing header and if found, we will generate a // trace event with the max payload ingress error. // Do this only for CLIENT connections. - if c.kind == CLIENT && len(remaining) > 0 { - if td := getHeader(MsgTraceDest, remaining); len(td) > 0 { - c.initAndSendIngressErrEvent(remaining, string(td), ErrMaxPayload) + if c.kind == CLIENT && c.pa.hdr > 0 && len(remaining) > 0 { + hdr := remaining[:min(len(remaining), c.pa.hdr)] + if td, ok := c.allowedMsgTraceDest(hdr, false); ok && td != _EMPTY_ { + c.initAndSendIngressErrEvent(hdr, td, ErrMaxPayload) } } c.maxPayloadViolation(c.pa.size, maxPayload) @@ -3240,10 +3245,12 @@ func (c *client) canSubscribe(subject string, optQueue ...string) bool { queue = optQueue[0] } - // For CLIENT connections that are MQTT, or other types of connections, we will - // implicitly allow anything that starts with the "$MQTT." prefix. However, + // For CLIENT connections that are MQTT we will implicitly allow anything that starts with + // the "$MQTT.sub." or "$MQTT.deliver.pubrel." prefix. For other types of connections, we + // will implicitly allow anything that starts with the full "$MQTT." prefix. However, // we don't just return here, we skip the check for "allow" but will check "deny". - if (c.isMqtt() || (c.kind != CLIENT)) && strings.HasPrefix(subject, mqttPrefix) { + if (c.isMqtt() && (strings.HasPrefix(subject, mqttSubPrefix) || strings.HasPrefix(subject, mqttPubRelDeliverySubjectPrefix))) || + (c.kind != CLIENT && strings.HasPrefix(subject, mqttPrefix)) { checkAllow = false } // Check allow list. If no allow list that means all are allowed. Deny can overrule. @@ -4053,6 +4060,41 @@ func (c *client) pubAllowed(subject string) bool { return c.pubAllowedFullCheck(subject, true, false) } +// allowedMsgTraceDest returns the trace destination if present and authorized. +// It only considers static publish permissions and does not consume dynamic +// reply permissions because the client is not publishing the trace event itself. +func (c *client) allowedMsgTraceDest(hdr []byte, hasLock bool) (string, bool) { + if len(hdr) == 0 { + return _EMPTY_, true + } + td := sliceHeader(MsgTraceDest, hdr) + if len(td) == 0 { + return _EMPTY_, true + } + dest := bytesToString(td) + if c.kind == CLIENT { + if hasGWRoutedReplyPrefix(td) { + return dest, false + } + var acc *Account + var srv *Server + if !hasLock { + c.mu.Lock() + } + acc, srv = c.acc, c.srv + if !hasLock { + c.mu.Unlock() + } + if bytes.HasPrefix(td, clientNRGPrefix) && srv != nil && acc != srv.SystemAccount() { + return dest, false + } + } + if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(dest, false, hasLock) { + return dest, false + } + return dest, true +} + // pubAllowedFullCheck checks on all publish permissioning depending // on the flag for dynamic reply permissions. func (c *client) pubAllowedFullCheck(subject string, fullCheck, hasLock bool) bool { @@ -4065,10 +4107,10 @@ func (c *client) pubAllowedFullCheck(subject string, fullCheck, hasLock bool) bo return v.(bool) } allowed, checkAllow := true, true - // For CLIENT connections that are MQTT, or other types of connections, we will - // implicitly allow anything that starts with the "$MQTT." prefix. However, - // we don't just return here, we skip the check for "allow" but will check "deny". - if (c.isMqtt() || c.kind != CLIENT) && strings.HasPrefix(subject, mqttPrefix) { + // For any connections, other than CLIENT, we will implicitly allow anything that + // starts with the "$MQTT." prefix. However, we don't just return here, + // we skip the check for "allow" but will check "deny". + if c.kind != CLIENT && strings.HasPrefix(subject, mqttPrefix) { checkAllow = false } // Cache miss, check allow then deny as needed. @@ -4188,10 +4230,19 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) { genidAddr := &acc.sl.genid // Check pub permissions - if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(string(c.pa.subject), true, true) { - c.mu.Unlock() - c.pubPermissionViolation(c.pa.subject) - return false, true + if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) { + if !c.pubAllowedFullCheck(string(c.pa.subject), true, true) { + c.mu.Unlock() + c.pubPermissionViolation(c.pa.subject) + return false, true + } + } + if c.pa.hdr > 0 { + if td, ok := c.allowedMsgTraceDest(msg[:c.pa.hdr], true); !ok { + c.mu.Unlock() + c.pubPermissionViolation(stringToBytes(td)) + return false, true + } } c.mu.Unlock() @@ -4391,28 +4442,43 @@ func (c *client) setupResponseServiceImport(acc *Account, si *serviceImport, tra return rsi } -// Will remove a header if present. -func removeHeaderIfPresent(hdr []byte, key string) []byte { - start := getHeaderKeyIndex(key, hdr) - // key can't be first and we want to check that it is preceded by a '\n' - if start < 1 || hdr[start-1] != '\n' { - return hdr - } - index := start + len(key) - if index >= len(hdr) || hdr[index] != ':' { - return hdr - } - end := bytes.Index(hdr[start:], []byte(_CRLF_)) - if end < 0 { +// Will remove a status and description from the header if present. +func removeHeaderStatusIfPresent(hdr []byte) []byte { + k := []byte("NATS/1.0") + kl, i := len(k), bytes.IndexByte(hdr, '\r') + if !bytes.HasPrefix(hdr, k) || i <= kl { return hdr } - hdr = append(hdr[:start], hdr[start+end+len(_CRLF_):]...) - if len(hdr) <= len(emptyHdrLine) { + hdr = append(hdr[:kl], hdr[i:]...) + if len(hdr) == len(emptyHdrLine) { return nil } return hdr } +// Will remove a header if present. +func removeHeaderIfPresent(hdr []byte, key string) []byte { + for { + start := getHeaderKeyIndex(key, hdr) + // key can't be first and we want to check that it is preceded by a '\n' + if start < 1 || hdr[start-1] != '\n' { + return hdr + } + index := start + len(key) + if index >= len(hdr) || hdr[index] != ':' { + return hdr + } + end := bytes.Index(hdr[start:], []byte(_CRLF_)) + if end < 0 { + return hdr + } + hdr = append(hdr[:start], hdr[start+end+len(_CRLF_):]...) + if len(hdr) <= len(emptyHdrLine) { + return nil + } + } +} + func removeHeaderIfPrefixPresent(hdr []byte, prefix string) []byte { var index int for { @@ -4747,16 +4813,33 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt if !isResponse { isSysImport := siAcc == c.srv.SystemAccount() var ci *ClientInfo - if hadPrevSi && c.pa.hdr >= 0 { - var cis ClientInfo - if err := json.Unmarshal(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]), &cis); err == nil { - ci = &cis + var cis *ClientInfo + if c.pa.hdr >= 0 { + var hci ClientInfo + if err := json.Unmarshal(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]), &hci); err == nil { + cis = &hci + } + } + if c.kind == LEAF && c.pa.hdr >= 0 && len(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr])) > 0 { + // Leaf nodes may forward a Nats-Request-Info from a remote domain, + // but the local server must replace it with the identity of the + // authenticated leaf connection instead of trusting forwarded values. + ci = c.getClientInfo(share) + if hadPrevSi { ci.Service = acc.Name - // Check if we are moving into a share details account from a non-shared - // and add in server and cluster details. if !share && (si.share || isSysImport) { c.addServerAndClusterInfo(ci) } + } else if !share && isSysImport { + c.addServerAndClusterInfo(ci) + } + } else if hadPrevSi && cis != nil { + ci = cis + ci.Service = acc.Name + // Check if we are moving into a share details account from a non-shared + // and add in server and cluster details. + if !share && (si.share || isSysImport) { + c.addServerAndClusterInfo(ci) } } else if c.kind != LEAF || c.pa.hdr < 0 || len(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr])) == 0 { ci = c.getClientInfo(share) @@ -4764,12 +4847,6 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt if !share && isSysImport { c.addServerAndClusterInfo(ci) } - } else if c.kind == LEAF && (si.share || isSysImport) { - // We have a leaf header here for ci, augment as above. - ci = c.getClientInfo(si.share) - if !si.share && isSysImport { - c.addServerAndClusterInfo(ci) - } } // Set clientInfo if present. if ci != nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/client_proxyproto.go b/vendor/github.com/nats-io/nats-server/v2/server/client_proxyproto.go index cdfbda7609b..69040e95778 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/client_proxyproto.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/client_proxyproto.go @@ -131,19 +131,22 @@ func detectProxyProtoVersion(conn net.Conn) (version int, header []byte, err err // readProxyProtoV1Header parses PROXY protocol v1 text format. // Expects the "PROXY " prefix (6 bytes) to have already been consumed. -func readProxyProtoV1Header(conn net.Conn) (*proxyProtoAddr, error) { +// Returns any bytes that were read past the trailing CRLF so the caller can +// replay them into the next protocol layer. +func readProxyProtoV1Header(conn net.Conn) (*proxyProtoAddr, []byte, error) { // Read rest of line (max 107 bytes total, already read 6) maxRemaining := proxyProtoV1MaxLineLen - 6 // Read up to maxRemaining bytes at once (more efficient than byte-by-byte) buf := make([]byte, maxRemaining) var line []byte + var remaining []byte for len(line) < maxRemaining { // Read available data n, err := conn.Read(buf[len(line):]) if err != nil { - return nil, fmt.Errorf("failed to read v1 line: %w", err) + return nil, nil, fmt.Errorf("failed to read v1 line: %w", err) } line = buf[:len(line)+n] @@ -151,7 +154,8 @@ func readProxyProtoV1Header(conn net.Conn) (*proxyProtoAddr, error) { // Look for CRLF in what we've read so far for i := 0; i < len(line)-1; i++ { if line[i] == '\r' && line[i+1] == '\n' { - // Found CRLF - extract just the line portion + // Found CRLF - keep any over-read bytes for the client parser. + remaining = append(remaining, line[i+2:]...) line = line[:i] goto foundCRLF } @@ -159,7 +163,7 @@ func readProxyProtoV1Header(conn net.Conn) (*proxyProtoAddr, error) { } // Exceeded max length without finding CRLF - return nil, fmt.Errorf("%w: v1 line too long", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: v1 line too long", errProxyProtoInvalid) foundCRLF: // Get parts from the protocol @@ -167,17 +171,17 @@ foundCRLF: // Validate format if len(parts) < 1 { - return nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid) } // Handle UNKNOWN (health check, like v2 LOCAL) if parts[0] == proxyProtoV1Unknown { - return nil, nil + return nil, remaining, nil } // Must have exactly 5 parts: protocol, src-ip, dst-ip, src-port, dst-port if len(parts) != 5 { - return nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: invalid v1 format", errProxyProtoInvalid) } protocol := parts[0] @@ -185,29 +189,29 @@ foundCRLF: dstIP := net.ParseIP(parts[2]) if srcIP == nil || dstIP == nil { - return nil, fmt.Errorf("%w: invalid address", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: invalid address", errProxyProtoInvalid) } // Parse ports srcPort, err := strconv.ParseUint(parts[3], 10, 16) if err != nil { - return nil, fmt.Errorf("invalid source port: %w", err) + return nil, nil, fmt.Errorf("invalid source port: %w", err) } dstPort, err := strconv.ParseUint(parts[4], 10, 16) if err != nil { - return nil, fmt.Errorf("invalid dest port: %w", err) + return nil, nil, fmt.Errorf("invalid dest port: %w", err) } // Validate protocol matches IP version if protocol == proxyProtoV1TCP4 && srcIP.To4() == nil { - return nil, fmt.Errorf("%w: TCP4 with IPv6 address", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: TCP4 with IPv6 address", errProxyProtoInvalid) } if protocol == proxyProtoV1TCP6 && srcIP.To4() != nil { - return nil, fmt.Errorf("%w: TCP6 with IPv4 address", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: TCP6 with IPv4 address", errProxyProtoInvalid) } if protocol != proxyProtoV1TCP4 && protocol != proxyProtoV1TCP6 { - return nil, fmt.Errorf("%w: invalid protocol %s", errProxyProtoInvalid, protocol) + return nil, nil, fmt.Errorf("%w: invalid protocol %s", errProxyProtoInvalid, protocol) } return &proxyProtoAddr{ @@ -215,25 +219,27 @@ foundCRLF: srcPort: uint16(srcPort), dstIP: dstIP, dstPort: uint16(dstPort), - }, nil + }, remaining, nil } // readProxyProtoHeader reads and parses PROXY protocol (v1 or v2) from the connection. // Automatically detects version and routes to appropriate parser. // If the command is LOCAL/UNKNOWN (health check), it returns nil for addr and no error. // If the command is PROXY, it returns the parsed address information. +// It also returns any bytes that were read past the v1 header terminator so the +// caller can replay them into the normal client parser. // The connection must be fresh (no data read yet). -func readProxyProtoHeader(conn net.Conn) (*proxyProtoAddr, error) { +func readProxyProtoHeader(conn net.Conn) (*proxyProtoAddr, []byte, error) { // Set read deadline to prevent hanging on slow/malicious clients if err := conn.SetReadDeadline(time.Now().Add(proxyProtoReadTimeout)); err != nil { - return nil, err + return nil, nil, err } defer conn.SetReadDeadline(time.Time{}) // Detect version version, firstBytes, err := detectProxyProtoVersion(conn) if err != nil { - return nil, err + return nil, nil, err } switch version { @@ -244,25 +250,26 @@ func readProxyProtoHeader(conn net.Conn) (*proxyProtoAddr, error) { // Read rest of v2 signature (bytes 6-11, total 6 more bytes) remaining := make([]byte, 6) if _, err := io.ReadFull(conn, remaining); err != nil { - return nil, fmt.Errorf("failed to read v2 signature: %w", err) + return nil, nil, fmt.Errorf("failed to read v2 signature: %w", err) } // Verify full signature fullSig := string(firstBytes) + string(remaining) if fullSig != proxyProtoV2Sig { - return nil, fmt.Errorf("%w: invalid signature", errProxyProtoInvalid) + return nil, nil, fmt.Errorf("%w: invalid signature", errProxyProtoInvalid) } // Read rest of header: ver/cmd, fam/proto, addr-len (4 bytes) header := make([]byte, 4) if _, err := io.ReadFull(conn, header); err != nil { - return nil, fmt.Errorf("failed to read v2 header: %w", err) + return nil, nil, fmt.Errorf("failed to read v2 header: %w", err) } // Continue with parsing - return parseProxyProtoV2Header(conn, header) + addr, err := parseProxyProtoV2Header(conn, header) + return addr, nil, err default: - return nil, fmt.Errorf("unsupported PROXY protocol version: %d", version) + return nil, nil, fmt.Errorf("unsupported PROXY protocol version: %d", version) } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/const.go b/vendor/github.com/nats-io/nats-server/v2/server/const.go index 0428275fc5a..410fc5e601a 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/const.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/const.go @@ -66,7 +66,7 @@ func init() { const ( // VERSION is the current version for the server. - VERSION = "2.12.4" + VERSION = "2.12.6" // PROTO is the currently supported protocol. // 0 was the original diff --git a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go index 668aad917e1..82233d926f8 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go @@ -42,7 +42,6 @@ import ( const ( JSPullRequestPendingMsgs = "Nats-Pending-Messages" JSPullRequestPendingBytes = "Nats-Pending-Bytes" - JSPullRequestWrongPinID = "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n\r\n" JSPullRequestNatsPinId = "Nats-Pin-Id" ) @@ -512,7 +511,7 @@ type consumer struct { // Details described in ADR-42. // currentPinId is the current nuid for the pinned consumer. - // If the Consumer is running in `PriorityPinnedClient` mode, server will + // If the Consumer is running in `PriorityPinnedClient` mode, server will // pick up a new nuid and assign it to first pending pull request. currentPinId string /// pinnedTtl is the remaining time before the current PinId expires. @@ -825,7 +824,7 @@ func checkConsumerCfg( return NewJSStreamInvalidConfigError(ErrBadSubject) } for inner, ssubject := range subjectFilters { - if inner != outer && SubjectsCollide(subject, ssubject) { + if inner != outer && subjectIsSubsetMatch(subject, ssubject) { return NewJSConsumerOverlappingSubjectFiltersError() } } @@ -1059,17 +1058,22 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri return nil, NewJSConsumerDoesNotExistError() } - // Check for any limits, if the config for the consumer sets a limit we check against that - // but if not we use the value from account limits, if account limits is more restrictive - // than stream config we prefer the account limits to handle cases where account limits are - // updated during the lifecycle of the stream - maxc := cfg.MaxConsumers - if maxc <= 0 || (selectedLimits.MaxConsumers > 0 && selectedLimits.MaxConsumers < maxc) { - maxc = selectedLimits.MaxConsumers - } - if maxc > 0 && mset.numPublicConsumers() >= maxc { - mset.mu.Unlock() - return nil, NewJSMaximumConsumersLimitError() + // If we're clustered we've already done this check, only do this if we're a standalone server. + // But if we're standalone, only enforce if we're not recovering, since the MaxConsumers could've + // been updated while we already had more consumers on disk. + if !s.JetStreamIsClustered() && s.standAloneMode() && !isRecovering { + // Check for any limits, if the config for the consumer sets a limit we check against that + // but if not we use the value from account limits, if account limits is more restrictive + // than stream config we prefer the account limits to handle cases where account limits are + // updated during the lifecycle of the stream + maxc := cfg.MaxConsumers + if maxc <= 0 || (selectedLimits.MaxConsumers > 0 && selectedLimits.MaxConsumers < maxc) { + maxc = selectedLimits.MaxConsumers + } + if maxc > 0 && mset.numPublicConsumers() >= maxc { + mset.mu.Unlock() + return nil, NewJSMaximumConsumersLimitError() + } } // Check on stream type conflicts with WorkQueues. @@ -1215,14 +1219,13 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri // If we have multiple filter subjects, create a sublist which we will use // in calling store.LoadNextMsgMulti. - if len(o.cfg.FilterSubjects) > 0 { + if len(o.subjf) <= 1 { + o.filters = nil + } else { o.filters = gsl.NewSublist[struct{}]() - for _, filter := range o.cfg.FilterSubjects { - o.filters.Insert(filter, struct{}{}) + for _, filter := range o.subjf { + o.filters.Insert(filter.subject, struct{}{}) } - } else { - // Make sure this is nil otherwise. - o.filters = nil } if o.store != nil && o.store.HasState() { @@ -1402,8 +1405,12 @@ func (o *consumer) monitorQuitC() <-chan struct{} { if o == nil { return nil } - o.mu.RLock() - defer o.mu.RUnlock() + o.mu.Lock() + defer o.mu.Unlock() + // Recreate if a prior monitor routine was stopped. + if o.mqch == nil { + o.mqch = make(chan struct{}) + } return o.mqch } @@ -1686,6 +1693,7 @@ func (o *consumer) setLeader(isLeader bool) { } else if o.srv.gateway.enabled { stopAndClearTimer(&o.gwdtmr) } + o.unassignPinId() // If we were the leader make sure to drain queued up acks. if wasLeader { o.ackMsgs.drain() @@ -2045,6 +2053,7 @@ func (o *consumer) deleteNotActive() { if o.srv != nil { qch = o.srv.quitCh } + oqch := o.qch o.mu.Unlock() if js != nil { cqch = js.clusterQuitC() @@ -2093,6 +2102,9 @@ func (o *consumer) deleteNotActive() { return case <-cqch: return + case <-oqch: + // The consumer has stopped already, likely by an earlier delete proposal being applied. + return } js.mu.RLock() if js.shuttingDown { @@ -2840,14 +2852,10 @@ func (o *consumer) releaseAnyPendingRequests(isAssigned bool) { if o.mset == nil || o.outq == nil || o.waiting.len() == 0 { return } - var hdr []byte - if !isAssigned { - hdr = []byte("NATS/1.0 409 Consumer Deleted\r\n\r\n") - } - wq := o.waiting for wr := wq.head; wr != nil; { - if hdr != nil { + if !isAssigned { + hdr := []byte("NATS/1.0 409 Consumer Deleted\r\n\r\n") o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) } next := wr.next @@ -3051,6 +3059,11 @@ func (o *consumer) setStoreState(state *ConsumerState) error { err := o.store.Update(state) if err == nil { o.applyState(state) + } else if err == ErrStoreOldUpdate { + // Our store already has a newer state, which is normal during recovery + // when the consumer was loaded from disk before the meta snapshot state + // was applied. + return nil } return err } @@ -3915,7 +3928,12 @@ func (o *consumer) setPinnedTimer(priorityGroup string) { } else { o.pinnedTtl = time.AfterFunc(o.cfg.PinnedTTL, func() { o.mu.Lock() - o.currentPinId = _EMPTY_ + // Skip if already unset. + if o.currentPinId == _EMPTY_ { + o.mu.Unlock() + return + } + o.unassignPinId() o.sendUnpinnedAdvisoryLocked(priorityGroup, "timeout") o.mu.Unlock() o.signalNewMessages() @@ -3923,6 +3941,28 @@ func (o *consumer) setPinnedTimer(priorityGroup string) { } } +// Lock should be held. +func (o *consumer) assignNewPinId(wr *waitingRequest) { + if wr.priorityGroup == nil || wr.priorityGroup.Group == _EMPTY_ { + return + } + o.currentPinId = nuid.Next() + o.pinnedTS = time.Now().UTC() + wr.priorityGroup.Id = o.currentPinId + o.setPinnedTimer(wr.priorityGroup.Group) + o.sendPinnedAdvisoryLocked(wr.priorityGroup.Group) +} + +// Lock should be held. +func (o *consumer) unassignPinId() { + o.currentPinId = _EMPTY_ + o.pinnedTS = time.Time{} + if o.pinnedTtl != nil { + o.pinnedTtl.Stop() + o.pinnedTtl = nil + } +} + // Return next waiting request. This will check for expirations but not noWait or interest. // That will be handled by processWaiting. // Lock should be held. @@ -3933,11 +3973,6 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest { // Check if server needs to assign a new pin id. needNewPin := o.currentPinId == _EMPTY_ && o.cfg.PriorityPolicy == PriorityPinnedClient - // As long as we support only one priority group, we can capture that group here and reuse it. - var priorityGroup string - if len(o.cfg.PriorityGroups) > 0 { - priorityGroup = o.cfg.PriorityGroups[0] - } numCycled := 0 for wr := o.waiting.peek(); !o.waiting.isEmpty(); wr = o.waiting.peek() { @@ -3971,15 +4006,12 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest { if wr.expires.IsZero() || time.Now().Before(wr.expires) { if needNewPin { if wr.priorityGroup.Id == _EMPTY_ { - o.currentPinId = nuid.Next() - o.pinnedTS = time.Now().UTC() - wr.priorityGroup.Id = o.currentPinId - o.setPinnedTimer(priorityGroup) - + o.assignNewPinId(wr) } else { // There is pin id set, but not a matching one. Send a notification to the client and remove the request. // Probably this is the old pin id. - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, []byte(JSPullRequestWrongPinID), nil, nil, 0)) + hdr := fmt.Appendf(nil, "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) + o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) o.waiting.removeCurrent() if o.node != nil { o.removeClusterPendingRequest(wr.reply) @@ -4000,7 +4032,8 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest { continue } else { // There is pin id set, but not a matching one. Send a notification to the client and remove the request. - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, []byte(JSPullRequestWrongPinID), nil, nil, 0)) + hdr := fmt.Appendf(nil, "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) + o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) o.waiting.removeCurrent() if o.node != nil { o.removeClusterPendingRequest(wr.reply) @@ -4012,9 +4045,13 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest { if o.cfg.PriorityPolicy == PriorityOverflow { if wr.priorityGroup != nil && + // If both limits are zero we don't cycle and the request will be fulfilled. + (wr.priorityGroup.MinPending > 0 || wr.priorityGroup.MinAckPending > 0) && // We need to check o.npc+1, because before calling nextWaiting, we do o.npc-- - (wr.priorityGroup.MinPending > 0 && wr.priorityGroup.MinPending > o.npc+1 || - wr.priorityGroup.MinAckPending > 0 && wr.priorityGroup.MinAckPending > int64(len(o.pending))) { + // If one OR the other limit is exceeded, we want to fulfill the request. + // This is an inverted check. For clarity, we check the positive condition and negate. + !((wr.priorityGroup.MinPending > 0 && wr.priorityGroup.MinPending <= o.npc+1) || + (wr.priorityGroup.MinAckPending > 0 && wr.priorityGroup.MinAckPending <= int64(len(o.pending)))) { o.waiting.cycle() numCycled++ // We're done cycling through the requests. @@ -4025,19 +4062,10 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest { } } if wr.acc.sl.HasInterest(wr.interest) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy) } else if time.Since(wr.received) < defaultGatewayRecentSubExpiration && (o.srv.leafNodeEnabled || o.srv.gateway.enabled) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy) } else if o.srv.gateway.enabled && o.srv.hasGatewayInterest(wr.acc.Name, wr.interest) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy) } } else { @@ -4195,15 +4223,7 @@ func (o *consumer) processNextMsgRequest(reply string, msg []byte) { sendErr(400, "Bad Request - Priority Group missing") return } - - found := false - for _, group := range o.cfg.PriorityGroups { - if group == priorityGroup.Group { - found = true - break - } - } - if !found { + if !slices.Contains(o.cfg.PriorityGroups, priorityGroup.Group) { sendErr(400, "Bad Request - Invalid Priority Group") return } @@ -4441,6 +4461,8 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) { // scheduled for redelivery, but it has been removed from the stream. // o.processTerm is called in a goroutine so could run after we get here. // That will correct the pending state and delivery/ack floors, so just skip here. + pmsg.returnToPool() + pmsg = nil continue } return pmsg, dc, err @@ -4468,6 +4490,7 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) { sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg) if sm == nil || err != nil { pmsg.returnToPool() + pmsg = nil } o.sseq++ return pmsg, 1, err @@ -4979,6 +5002,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { o.addToRedeliverQueue(pmsg.seq) } pmsg.returnToPool() + pmsg = nil goto waitForMsgs } @@ -4989,6 +5013,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { select { case <-qch: pmsg.returnToPool() + pmsg = nil return case <-time.After(delay): } @@ -5009,6 +5034,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { select { case <-qch: pmsg.returnToPool() + pmsg = nil return case <-time.After(delay): } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/errors.go b/vendor/github.com/nats-io/nats-server/v2/server/errors.go index 07a3ca696f4..ce0a9beb1c6 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/errors.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/errors.go @@ -215,6 +215,9 @@ var ( // ErrMinimumVersionRequired is returned when a connection is not at the minimum version required. ErrMinimumVersionRequired = errors.New("minimum version required") + // ErrLeafNodeMinVersionRejected is the leafnode protocol error prefix used + // when rejecting a remote due to leafnodes.min_version. + ErrLeafNodeMinVersionRejected = errors.New("connection rejected since minimum version required is") // ErrInvalidMappingDestination is used for all subject mapping destination errors ErrInvalidMappingDestination = errors.New("invalid mapping destination") @@ -251,6 +254,9 @@ type mappingDestinationErr struct { } func (e *mappingDestinationErr) Error() string { + if e.token == _EMPTY_ { + return e.err.Error() + } return fmt.Sprintf("%s in %s", e.err, e.token) } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/events.go b/vendor/github.com/nats-io/nats-server/v2/server/events.go index 8bcb3a713fa..bfe190cd00e 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/events.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/events.go @@ -1500,6 +1500,8 @@ func (s *Server) initEventTracking() { type UserInfo struct { UserID string `json:"user"` Account string `json:"account"` + AccountName string `json:"account_name,omitempty"` + UserName string `json:"user_name,omitempty"` Permissions *Permissions `json:"permissions,omitempty"` Expires time.Duration `json:"expires,omitempty"` } @@ -1519,9 +1521,22 @@ func (s *Server) userInfoReq(sub *subscription, c *client, _ *Account, subject, return } + // Look up the requester's account directly from ci.Account rather than + // using the acc returned by getRequestInfo, which may resolve to the + // service account (ci.Service) when the request arrives via a chained + // service import. + var accountName string + if ci.Account != _EMPTY_ { + if reqAcc, _ := s.LookupAccount(ci.Account); reqAcc != nil { + accountName = reqAcc.getNameTag() + } + } + response.Data = &UserInfo{ UserID: ci.User, Account: ci.Account, + AccountName: accountName, + UserName: ci.NameTag, Permissions: c.publicPermissions(), Expires: c.claimExpiration(), } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go index e3f9e855fec..f68e5d76134 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go @@ -508,28 +508,39 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim return nil, err } + fs.mu.Lock() // Check if our prior state remembers a last sequence past where we can see. // Unless we're async flushing, in which case this can happen if some blocks weren't flushed. if prior.LastSeq > fs.state.LastSeq && !fs.fcfg.AsyncFlush { + if mb, err := fs.newMsgBlockForWrite(); err != nil { + fs.mu.Unlock() + return nil, err + } else if err = mb.writeTombstone(prior.LastSeq, prior.LastTime.UnixNano()); err != nil { + fs.mu.Unlock() + return nil, err + } fs.state.LastSeq, fs.state.LastTime = prior.LastSeq, prior.LastTime if fs.state.Msgs == 0 { fs.state.FirstSeq = fs.state.LastSeq + 1 fs.state.FirstTime = time.Time{} } - if fs.ld != nil { - if _, err := fs.newMsgBlockForWrite(); err == nil { - if err = fs.writeTombstone(prior.LastSeq, prior.LastTime.UnixNano()); err != nil { - return nil, err - } - } else { - return nil, err - } - } } // Since we recovered here, make sure to kick ourselves to write out our stream state. fs.dirty++ + fs.mu.Unlock() } + // Lock during the remainder of the recovery. + fs.mu.Lock() + // Use defer to ensure the lock is released if any of the enforcement operations + // run into issues to avoid potential deadlocks on exit. + unlocked := false + defer func() { + if !unlocked { + fs.mu.Unlock() + } + }() + // See if we can bring back our TTL timed hash wheel state from disk. if cfg.AllowMsgTTL { if err = fs.recoverTTLState(); err != nil && !os.IsNotExist(err) { @@ -552,9 +563,6 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim } }() - // Lock while we do enforcements and removals. - fs.mu.Lock() - // Check if we have any left over tombstones to process. if len(fs.tombs) > 0 { for _, seq := range fs.tombs { @@ -586,6 +594,7 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim // Grab first sequence for check below while we have lock. firstSeq := fs.state.FirstSeq fs.mu.Unlock() + unlocked = true // If the stream has an initial sequence number then make sure we // have purged up until that point. We will do this only if the @@ -1158,10 +1167,14 @@ func (fs *fileStore) recoverMsgBlock(index uint32) (*msgBlock, error) { var lchk [8]byte if mb.rbytes >= checksumSize { if mb.bek != nil { - if buf, _ := mb.loadBlock(nil); len(buf) >= checksumSize { + // We pass nil, so get a buf from the block pool, we'll need to recycle it afterward. + buf, _ := mb.loadBlock(nil) + if len(buf) >= checksumSize { mb.bek.XORKeyStream(buf, buf) copy(lchk[0:], buf[len(buf)-checksumSize:]) } + // We can recycle it now. + recycleMsgBlockBuf(buf) } else { file.ReadAt(lchk[:], int64(mb.rbytes)-checksumSize) } @@ -1576,15 +1589,15 @@ func (mb *msgBlock) rebuildStateFromBufLocked(buf []byte, allowTruncate bool) (* rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) hasHeaders := rl&hbit != 0 - var ttl int64 - if mb.fs.ttls != nil && len(hdr) > 0 { - ttl, _ = getMessageTTL(hdr) - } // Clear any headers bit that could be set. rl &^= hbit + shlen := slen + if hasHeaders { + shlen += 4 + } dlen := int(rl) - msgHdrSize // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { + if dlen < 0 || shlen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { truncate(index) return gatherLost(lbuf - index), tombstones, errBadMsg{mb.mfn, fmt.Sprintf("sanity check failed (dlen %d slen %d rl %d index %d lbuf %d)", dlen, slen, rl, index, lbuf)} } @@ -1661,21 +1674,6 @@ func (mb *msgBlock) rebuildStateFromBufLocked(buf []byte, allowTruncate bool) (* if !mb.dmap.Exists(seq) { mb.msgs++ mb.bytes += uint64(rl) - if ttl > 0 { - if mb.fs.ttls != nil { - expires := time.Duration(ts) + (time.Second * time.Duration(ttl)) - mb.fs.ttls.Add(seq, int64(expires)) - } - // Need to count these regardless as we might want to enable TTLs - // later via UpdateConfig. - mb.ttls++ - } - if mb.fs.scheduling != nil { - if schedule, ok := getMessageSchedule(hdr); ok && !schedule.IsZero() { - mb.fs.scheduling.add(seq, string(subj), schedule.UnixNano()) - mb.schedules++ - } - } } updateLast(seq, ts) @@ -1694,7 +1692,7 @@ func (mb *msgBlock) rebuildStateFromBufLocked(buf []byte, allowTruncate bool) (* } else if fseq == 0 && maxTombstoneSeq > 0 { atomic.StoreUint64(&mb.first.seq, maxTombstoneSeq+1) mb.first.ts = 0 - if mb.last.seq == 0 { + if lseq := atomic.LoadUint64(&mb.last.seq); lseq == 0 { atomic.StoreUint64(&mb.last.seq, maxTombstoneSeq) mb.last.ts = maxTombstoneTs } @@ -1726,13 +1724,15 @@ func (fs *fileStore) debug(format string, args ...any) { // Track local state but ignore timestamps here. func updateTrackingState(state *StreamState, mb *msgBlock) { + first := atomic.LoadUint64(&mb.first.seq) + last := atomic.LoadUint64(&mb.last.seq) if state.FirstSeq == 0 { - state.FirstSeq = mb.first.seq - } else if mb.first.seq < state.FirstSeq && mb.first.ts != 0 { - state.FirstSeq = mb.first.seq + state.FirstSeq = first + } else if first < state.FirstSeq && mb.first.ts != 0 { + state.FirstSeq = first } - if mb.last.seq > state.LastSeq { - state.LastSeq = mb.last.seq + if last > state.LastSeq { + state.LastSeq = last } state.Msgs += mb.msgs state.Bytes += mb.bytes @@ -2305,9 +2305,11 @@ func (fs *fileStore) recoverMsgs() error { if mb, err := fs.recoverMsgBlock(uint32(index)); err == nil && mb != nil { // This is a truncate block with possibly no index. If the OS got shutdown // out from underneath of us this is possible. - if mb.first.seq == 0 { + mb.mu.Lock() + if atomic.LoadUint64(&mb.first.seq) == 0 { mb.dirtyCloseWithRemove(true) fs.removeMsgBlockFromList(mb) + mb.mu.Unlock() continue } // If the stream is empty, reset the first/last sequences so these can @@ -2337,6 +2339,14 @@ func (fs *fileStore) recoverMsgs() error { } fs.state.Msgs += mb.msgs fs.state.Bytes += mb.bytes + // If the block is empty, correct the sequences to be aligned with the current filestore state. + if mb.msgs == 0 { + atomic.StoreUint64(&mb.first.seq, fs.state.LastSeq+1) + mb.first.ts = 0 + atomic.StoreUint64(&mb.last.seq, fs.state.LastSeq) + mb.last.ts = fs.state.LastTime.UnixNano() + } + mb.mu.Unlock() } else { return err } @@ -2565,6 +2575,7 @@ func (fs *fileStore) expireMsgsOnRecover() error { fs.selectNextFirst() // Check if we have no messages and blocks left. + if fs.lmb == nil && last.seq != 0 { if lmb, _ := fs.newMsgBlockForWrite(); lmb != nil { fs.writeTombstone(last.seq, last.ts) @@ -3524,6 +3535,11 @@ func (fs *fileStore) allLastSeqsLocked() ([]uint64, error) { mb.fss.IterFast(func(bsubj []byte, ss *SimpleState) bool { // Check if already been processed and accounted. if _, ok := subs[string(bsubj)]; !ok { + // Check if we need to recalculate. We only care about the last sequence. + if ss.lastNeedsUpdate { + // mb is already loaded into the cache so should be fast-ish. + mb.recalculateForSubj(bytesToString(bsubj), ss) + } seqs = append(seqs, ss.Last) subs[string(bsubj)] = struct{}{} } @@ -3550,6 +3566,7 @@ func (fs *fileStore) filterIsAll(filters []string) bool { } // Sort so we can compare. slices.Sort(filters) + slices.Sort(fs.cfg.Subjects) for i, subj := range filters { if !subjectIsSubsetMatch(fs.cfg.Subjects[i], subj) { return false @@ -4024,7 +4041,11 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) mb.mu.Unlock() } // Make final adjustment. - total -= adjust + if adjust > total { + total = 0 + } else { + total -= adjust + } return total, validThrough, nil } @@ -4116,7 +4137,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer var shouldExpire bool var updateLLTS bool // We need to walk this block to correct accounting from above. - if sseq > mb.first.seq { + if sseq > atomic.LoadUint64(&mb.first.seq) { // Track the ones we add back in case more than one. seen := make(map[string]bool) // We need to discount the total by subjects seen before sseq, but also add them right back in if they are >= sseq for this blk. @@ -4358,7 +4379,11 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer mb.mu.Unlock() } // Make final adjustment. - total -= adjust + if adjust > total { + total = 0 + } else { + total -= adjust + } return total, validThrough, nil } @@ -4854,11 +4879,15 @@ func (fs *fileStore) SkipMsgs(seq uint64, num uint64) error { const maxDeletes = 64 * 1024 mb := fs.lmb + var msgs uint64 numDeletes := int(num) if mb != nil { + mb.mu.RLock() numDeletes += mb.dmap.Size() + msgs = mb.msgs + mb.mu.RUnlock() } - if mb == nil || numDeletes > maxDeletes && mb.msgs > 0 || mb.msgs > 0 && mb.blkSize()+emptyRecordLen > fs.fcfg.BlockSize { + if mb == nil || numDeletes > maxDeletes && msgs > 0 || msgs > 0 && mb.blkSize()+emptyRecordLen > fs.fcfg.BlockSize { var err error if mb, err = fs.newMsgBlockForWrite(); err != nil { return err @@ -4969,18 +4998,18 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { bsubj := stringToBytes(subj) if ss, ok := mb.fss.Find(bsubj); ok && ss != nil { - // Adjust first if it was not where we thought it should be. - if i != start { - if info, ok := fs.psim.Find(bsubj); ok { - info.fblk = i - } - } if ss.firstNeedsUpdate || ss.lastNeedsUpdate { mb.recalculateForSubj(subj, ss) } mb.mu.Unlock() // Re-acquire fs lock fs.mu.Lock() + // Adjust first if it was not where we thought it should be. + if i != start { + if info, ok := fs.psim.Find(bsubj); ok { + info.fblk = i + } + } return ss.First, nil } // If we did not find it and we loaded this msgBlock try to expire as long as not the last. @@ -5300,18 +5329,15 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( } } - var smv StoreMsg - var sm *StoreMsg - var err error - if secure { - // For a secure erase we can't use NoCopy, as eraseMsg will overwrite the - // cache and we won't be able to access sm.subj etc anymore later on. - sm, err = mb.cacheLookup(seq, &smv) - } else { - // For a non-secure erase it's fine to use NoCopy, as the cache won't change - // from underneath us. - sm, err = mb.cacheLookupNoCopy(seq, &smv) - } + var ( + smv StoreMsg + subj string + ts int64 + lhdr, lmsg int + ttl int64 + ) + // We don't use a copy as long as that's possible. When unlocking mb or erasing, we'll copy the subject. + sm, err := mb.cacheLookupNoCopy(seq, &smv) if err != nil { finishedWithCache() mb.mu.Unlock() @@ -5321,6 +5347,12 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( err = nil } return false, err + } else if sm != nil { + subj = sm.subj + ts = sm.ts + lhdr = len(sm.hdr) + lmsg = len(sm.msg) + ttl, _ = getMessageTTL(sm.hdr) } // Check if we need to write a deleted record tombstone. @@ -5328,6 +5360,8 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( // when the last block is empty. // If not via limits and not empty (empty writes tombstone below if last) write tombstone. if !viaLimits && !isEmpty && sm != nil { + // Need to copy the subject since we unlock and re-acquire, and the cache could change. + subj = copyString(subj) mb.mu.Unlock() // Only safe way to checkLastBlock is to unlock here... lmb, err := fs.checkLastBlock(emptyRecordLen) if err != nil { @@ -5335,7 +5369,7 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( fsUnlock() return false, err } - if err := lmb.writeTombstone(sm.seq, sm.ts); err != nil { + if err := lmb.writeTombstone(seq, ts); err != nil { finishedWithCache() fsUnlock() return false, err @@ -5344,7 +5378,7 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( } // Grab size - msz := fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) + msz := fileStoreMsgSizeRaw(len(subj), lhdr, lmsg) // Set cache timestamp for last remove. mb.lrts = ats.AccessTime() @@ -5359,6 +5393,9 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( fsUnlock() return false, err } + // Need to copy the subject, as eraseMsg will overwrite the cache and we won't + // be able to access sm.subj anymore later on. + subj = copyString(subj) if err := mb.eraseMsg(seq, int(ri), int(msz), isLastBlock); err != nil { finishedWithCache() mb.mu.Unlock() @@ -5397,13 +5434,11 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( mb.ensurePerSubjectInfoLoaded() // If we are tracking multiple subjects here make sure we update that accounting. - mb.removeSeqPerSubject(sm.subj, seq) - fs.removePerSubject(sm.subj) - if fs.ttls != nil { - if ttl, err := getMessageTTL(sm.hdr); err == nil { - expires := time.Duration(sm.ts) + (time.Second * time.Duration(ttl)) - fs.ttls.Remove(seq, int64(expires)) - } + mb.removeSeqPerSubject(subj, seq) + fs.removePerSubject(subj) + if fs.ttls != nil && ttl > 0 { + expires := time.Duration(ts) + (time.Second * time.Duration(ttl)) + fs.ttls.Remove(seq, int64(expires)) } if fifo { @@ -5461,7 +5496,7 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) ( fs.mu.Unlock() // Storage updates. delta := int64(msz) - cb(-1, -delta, seq, sm.subj) + cb(-1, -delta, seq, subj) if !needFSLock { fs.mu.Lock() @@ -5532,11 +5567,16 @@ func (mb *msgBlock) compactWithFloor(floor uint64) error { } hdr := buf[index : index+msgHdrSize] rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) + hasHeaders := rl&hbit != 0 // Clear any headers bit that could be set. rl &^= hbit + shlen := slen + if hasHeaders { + shlen += 4 + } dlen := int(rl) - msgHdrSize // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { + if dlen < 0 || shlen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { return fmt.Errorf("sanity check failed") } // Only need to process non-deleted messages. @@ -5574,15 +5614,16 @@ func (mb *msgBlock) compactWithFloor(floor uint64) error { // Handle compression if mb.cmp != NoCompression && len(nbuf) > 0 { - cbuf, err := mb.cmp.Compress(nbuf) - if err != nil { + originalSize := len(nbuf) + var err error + if nbuf, err = mb.cmp.Compress(nbuf); err != nil { return err } meta := &CompressionInfo{ Algorithm: mb.cmp, - OriginalSize: uint64(len(nbuf)), + OriginalSize: uint64(originalSize), } - nbuf = append(meta.MarshalMetadata(), cbuf...) + nbuf = append(meta.MarshalMetadata(), nbuf...) } // Check for encryption. @@ -5916,10 +5957,10 @@ func (mb *msgBlock) truncate(tseq uint64, ts int64) (nmsgs, nbytes uint64, err e } } - // If the block is compressed then we have to load it into memory - // and decompress it, truncate it and then write it back out. + // If the block is compressed/encrypted then we have to load it into memory + // and decompress/decrypt it, truncate it and then write it back out. // Otherwise, truncate the file itself and close the descriptor. - if mb.cmp != NoCompression { + if mb.cmp != NoCompression || mb.bek != nil { buf, err := mb.loadBlock(nil) if err != nil { return 0, 0, fmt.Errorf("failed to load block from disk: %w", err) @@ -5931,7 +5972,7 @@ func (mb *msgBlock) truncate(tseq uint64, ts int64) (nmsgs, nbytes uint64, err e return 0, 0, fmt.Errorf("failed to decompress block: %w", err) } buf = buf[:eof] - copy(mb.lchk[0:], buf[:len(buf)-checksumSize]) + copy(mb.lchk[0:], buf[len(buf)-checksumSize:]) // We did decompress but don't recompress the truncated buffer here since we're the last block // and would otherwise have compressed data and allow to write uncompressed data in the same block. if err = mb.atomicOverwriteFile(buf, false); err != nil { @@ -6974,6 +7015,7 @@ func (mb *msgBlock) atomicOverwriteFile(buf []byte, allowCompress bool) error { // The original buffer at this point is uncompressed, so we will now compress // it if needed. Note that if the selected algorithm is NoCompression, the // Compress function will just return the input buffer unmodified. + originalSize := len(buf) if buf, err = alg.Compress(buf); err != nil { return errorCleanup(fmt.Errorf("failed to compress block: %w", err)) } @@ -6983,7 +7025,7 @@ func (mb *msgBlock) atomicOverwriteFile(buf []byte, allowCompress bool) error { // writing metadata. meta := &CompressionInfo{ Algorithm: alg, - OriginalSize: uint64(len(buf)), + OriginalSize: uint64(originalSize), } buf = append(meta.MarshalMetadata(), buf...) } @@ -7331,7 +7373,7 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { lbuf := uint32(len(buf)) var seq, ttls, schedules uint64 - var sm StoreMsg // Used for finding TTL headers + var sm StoreMsg // Used for finding headers // To ensure the sequence keeps moving up. As well as confirming our index // is aligned with the mb's first and last sequence. @@ -7346,13 +7388,16 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) seq = le.Uint64(hdr[4:]) - // Clear any headers bit that could be set. hasHeaders := rl&hbit != 0 + // Clear any headers bit that could be set. rl &^= hbit + shlen := slen + if hasHeaders { + shlen += 4 + } dlen := int(rl) - msgHdrSize - // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { + if dlen < 0 || shlen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { mb.fs.warn("indexCacheBuf corrupt record state in %s: dlen %d slen %d index %d rl %d lbuf %d", mb.mfn, dlen, slen, index, rl, lbuf) // This means something is off. // TODO(dlc) - Add into bad list? @@ -8089,8 +8134,13 @@ func (mb *msgBlock) msgFromBufEx(buf []byte, sm *StoreMsg, hh *highwayhash.Diges rl &^= hbit // clear header bit dlen := int(rl) - msgHdrSize slen := int(le.Uint16(hdr[20:])) + + shlen := slen + if hasHeaders { + shlen += 4 + } // Simple sanity check. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || int(rl) > len(buf) || rl > rlBadThresh { + if dlen < 0 || shlen > (dlen-recordHashSize) || dlen > int(rl) || int(rl) > len(buf) || rl > rlBadThresh { return nil, errBadMsg{mb.mfn, fmt.Sprintf("sanity check failed (dlen %d slen %d rl %d buf %d)", dlen, slen, rl, buf)} } data := buf[msgHdrSize : msgHdrSize+dlen] @@ -8192,7 +8242,8 @@ func (fs *fileStore) SubjectForSeq(seq uint64) (string, error) { fs.mu.RUnlock() if mb != nil { if sm, _, _ := mb.fetchMsgNoCopy(seq, &smv); sm != nil { - return sm.subj, nil + // Copy the subject, as it's used elsewhere, and the backing cache could be reused in the meantime. + return copyString(sm.subj), nil } } return _EMPTY_, ErrStoreMsgNotFound @@ -8265,6 +8316,11 @@ func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err err // Optimize if subject is not a wildcard. if !wc { if ss, ok := mb.fss.Find(stringToBytes(subj)); ok && ss != nil { + // Check if we need to recalculate. We only care about the last sequence. + if ss.lastNeedsUpdate { + // mb is already loaded into the cache so should be fast-ish. + mb.recalculateForSubj(subj, ss) + } l = ss.Last } } @@ -8824,7 +8880,9 @@ func (fs *fileStore) cacheLoads() uint64 { var tl uint64 fs.mu.RLock() for _, mb := range fs.blks { + mb.mu.RLock() tl += mb.cloads + mb.mu.RUnlock() } fs.mu.RUnlock() return tl @@ -8835,7 +8893,7 @@ func (fs *fileStore) cacheSize() uint64 { var sz uint64 fs.mu.RLock() for _, mb := range fs.blks { - mb.mu.RLock() + mb.mu.Lock() var needsCleanup bool if mb.cache == nil { mb.cache = mb.ecache.Value() @@ -8847,7 +8905,7 @@ func (fs *fileStore) cacheSize() uint64 { if needsCleanup { mb.finishedWithCache() } - mb.mu.RUnlock() + mb.mu.Unlock() } fs.mu.RUnlock() return sz @@ -8946,6 +9004,7 @@ func (fs *fileStore) PurgeEx(subject string, sequence, keep uint64) (purged uint if mb.cacheNotLoaded() { if err := mb.loadMsgsWithLock(); err != nil { mb.mu.Unlock() + fs.mu.Unlock() return 0, err } shouldExpire = true @@ -9049,6 +9108,7 @@ func (fs *fileStore) PurgeEx(subject string, sequence, keep uint64) (purged uint if len(tombs) > 0 { for _, tomb := range tombs { if err := fs.writeTombstoneNoFlush(tomb.seq, tomb.ts); err != nil { + fs.mu.Unlock() return purged, err } } @@ -9368,6 +9428,7 @@ SKIP: if len(tombs) > 0 { for _, tomb := range tombs { if err := fs.writeTombstoneNoFlush(tomb.seq, tomb.ts); err != nil { + fs.mu.Unlock() return purged, err } } @@ -9558,6 +9619,7 @@ func (fs *fileStore) Truncate(seq uint64) error { // If we end up not needing to write tombstones, this block will be cleaned up at the end. tmb, err := fs.newMsgBlockForWrite() if err != nil { + fs.mu.Unlock() return err } @@ -10159,6 +10221,29 @@ func (fs *fileStore) populateGlobalPerSubjectInfo(mb *msgBlock) { }) } +// Calls os.RemoveAll on the given `dir` directory, but if an error occurs, +// retries up to one second. If that still fails, returns the last error +// that os.RemoveAll returned. +func removeAllWithRetry(dir string) error { + <-dios + err := os.RemoveAll(dir) + dios <- struct{}{} + if err == nil { + return nil + } + ttl := time.Now().Add(time.Second) + for time.Now().Before(ttl) { + time.Sleep(10 * time.Millisecond) + <-dios + err = os.RemoveAll(dir) + dios <- struct{}{} + if err == nil { + return nil + } + } + return err +} + // Close the message block. func (mb *msgBlock) close(sync bool) { if mb == nil { @@ -10261,28 +10346,12 @@ func (fs *fileStore) Delete(inline bool) error { } // Do this in separate Go routine in case lots of blocks. // Purge above protects us as does the removal of meta artifacts above. - removeDir := func() { - <-dios - err := os.RemoveAll(ndir) - dios <- struct{}{} - if err == nil { - return - } - ttl := time.Now().Add(time.Second) - for time.Now().Before(ttl) { - time.Sleep(10 * time.Millisecond) - <-dios - err = os.RemoveAll(ndir) - dios <- struct{}{} - if err == nil { - return - } - } - } if inline { - removeDir() + if err := removeAllWithRetry(ndir); err != nil { + return err + } } else { - go removeDir() + go removeAllWithRetry(ndir) } return nil } @@ -10837,18 +10906,18 @@ func (fs *fileStore) streamSnapshot(w io.WriteCloser, includeConsumers bool, err const minLen = 32 sfn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) if buf, err := os.ReadFile(sfn); err == nil && len(buf) >= minLen { + fs.mu.Lock() if fs.aek != nil { ns := fs.aek.NonceSize() buf, err = fs.aek.Open(nil, buf[:ns], buf[ns:len(buf)-highwayhash.Size64], nil) if err == nil { // Redo hash checksum at end on plaintext. - fs.mu.Lock() hh.Reset() hh.Write(buf) buf = fs.hh.Sum(buf) - fs.mu.Unlock() } } + fs.mu.Unlock() if err == nil && writeFile(msgPre+streamStreamStateFile, buf) != nil { return } @@ -11639,7 +11708,7 @@ func (o *consumerFileStore) Update(state *ConsumerState) error { // Check to see if this is an outdated update. if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream { - return fmt.Errorf("old update ignored") + return ErrStoreOldUpdate } o.state.Delivered = state.Delivered @@ -11703,6 +11772,7 @@ func (o *consumerFileStore) writeState(buf []byte) error { if o.aek != nil { var err error if buf, err = o.encryptState(buf); err != nil { + o.mu.Unlock() return err } } @@ -12068,6 +12138,7 @@ func (o *consumerFileStore) Stop() error { if buf, err = o.encodeState(); err == nil && len(buf) > 0 { if o.aek != nil { if buf, err = o.encryptState(buf); err != nil { + o.mu.Unlock() return err } } @@ -12122,7 +12193,6 @@ func (o *consumerFileStore) delete(streamDeleted bool) error { o.qch = nil } - var err error odir := o.odir o.odir = _EMPTY_ o.closed = true @@ -12131,16 +12201,18 @@ func (o *consumerFileStore) delete(streamDeleted bool) error { // If our stream was not deleted this will remove the directories. if odir != _EMPTY_ && !streamDeleted { - <-dios - err = os.RemoveAll(odir) - dios <- struct{}{} + if err := removeAllWithRetry(odir); err != nil { + return err + } } if !streamDeleted { - fs.RemoveConsumer(o) + if err := fs.RemoveConsumer(o); err != nil { + return err + } } - return err + return nil } func (fs *fileStore) AddConsumer(o ConsumerStore) error { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go index a74fc0d8219..3ec6942b905 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go @@ -1563,7 +1563,7 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits, tq c } // Add in the stream. - mset, err := a.addStream(&cfg.StreamConfig) + mset, err := a.recoverStream(&cfg.StreamConfig) if err != nil { s.Warnf(" Error recreating stream %q: %v", cfg.Name, err) // If we removed a keyfile from above make sure to put it back. @@ -2365,8 +2365,10 @@ func tierName(replicas int) string { } func isSameTier(cfgA, cfgB *StreamConfig) bool { + a := max(1, cfgA.Replicas) + b := max(1, cfgB.Replicas) // TODO (mh) this is where we could select based off a placement tag as well "qos:tier" - return cfgA.Replicas == cfgB.Replicas + return a == b } func (jsa *jsAccount) jetStreamAndClustered() (*jetStream, bool) { @@ -2441,17 +2443,11 @@ func (jsa *jsAccount) wouldExceedLimits(storeType StorageType, tierName string, // Since tiers are flat we need to scale limit up by replicas when checking. if storeType == MemoryStorage { totalMem := inUse.total.mem + (int64(memStoreMsgSize(subj, hdr, msg)) * r) - if selectedLimits.MemoryMaxStreamBytes > 0 && totalMem > selectedLimits.MemoryMaxStreamBytes*lr { - return true, nil - } if selectedLimits.MaxMemory >= 0 && totalMem > selectedLimits.MaxMemory*lr { return true, nil } } else { totalStore := inUse.total.store + (int64(fileStoreMsgSize(subj, hdr, msg)) * r) - if selectedLimits.StoreMaxStreamBytes > 0 && totalStore > selectedLimits.StoreMaxStreamBytes*lr { - return true, nil - } if selectedLimits.MaxStore >= 0 && totalStore > selectedLimits.MaxStore*lr { return true, nil } @@ -2490,25 +2486,25 @@ func (js *jetStream) checkBytesLimits(selectedLimits *JetStreamAccountLimits, ad if addBytes < 0 { addBytes = 1 } - totalBytes := addBytes + maxBytesOffset + totalBytes := addSaturate(addBytes, maxBytesOffset) switch storage { case MemoryStorage: // Account limits defined. - if selectedLimits.MaxMemory >= 0 && currentRes+totalBytes > selectedLimits.MaxMemory { + if selectedLimits.MaxMemory >= 0 && (currentRes > selectedLimits.MaxMemory || totalBytes > selectedLimits.MaxMemory-currentRes) { return NewJSMemoryResourcesExceededError() } // Check if this server can handle request. - if checkServer && js.memReserved+totalBytes > js.config.MaxMemory { + if checkServer && (js.memReserved > js.config.MaxMemory || totalBytes > js.config.MaxMemory-js.memReserved) { return NewJSMemoryResourcesExceededError() } case FileStorage: // Account limits defined. - if selectedLimits.MaxStore >= 0 && currentRes+totalBytes > selectedLimits.MaxStore { + if selectedLimits.MaxStore >= 0 && (currentRes > selectedLimits.MaxStore || totalBytes > selectedLimits.MaxStore-currentRes) { return NewJSStorageResourcesExceededError() } // Check if this server can handle request. - if checkServer && js.storeReserved+totalBytes > js.config.MaxStore { + if checkServer && (js.storeReserved > js.config.MaxStore || totalBytes > js.config.MaxStore-js.storeReserved) { return NewJSStorageResourcesExceededError() } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go index e2c25d21692..e8600e14f87 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go @@ -24,11 +24,10 @@ import ( "path/filepath" "runtime" "slices" - "strconv" "strings" + "sync" "sync/atomic" "time" - "unicode" "github.com/nats-io/nuid" ) @@ -563,9 +562,17 @@ type JSApiStreamSnapshotRequest struct { DeliverSubject string `json:"deliver_subject"` // Do not include consumers in the snapshot. NoConsumers bool `json:"no_consumers,omitempty"` - // Optional chunk size preference. - // Best to just let server select. + // Optional chunk size preference. Defaults to 128KB, + // automatically clamped to within the range 1KB to 1MB. + // A smaller chunk size means more in-flight messages + // and more acks needed. Links with good throughput + // but high latency may need to increase this. ChunkSize int `json:"chunk_size,omitempty"` + // Optional window size preference. Defaults to 8MB, + // automatically clamped to within the range 1KB to 32MB. + // very slow connections may need to reduce this to + // avoid slow consumer issues. + WindowSize int `json:"window_size,omitempty"` // Check all message's checksums prior to snapshot. CheckMsgs bool `json:"jsck,omitempty"` } @@ -600,7 +607,7 @@ const JSApiStreamRestoreResponseType = "io.nats.jetstream.api.v1.stream_restore_ // JSApiStreamRemovePeerRequest is the required remove peer request. type JSApiStreamRemovePeerRequest struct { - // Server name of the peer to be removed. + // Server name or peer ID of the peer to be removed. Peer string `json:"peer"` } @@ -1578,23 +1585,19 @@ func (s *Server) jsonResponse(v any) string { // Read lock must be held func (jsa *jsAccount) tieredReservation(tier string, cfg *StreamConfig) int64 { - reservation := int64(0) - if tier == _EMPTY_ { - for _, sa := range jsa.streams { - if sa.cfg.MaxBytes > 0 { - if sa.cfg.Storage == cfg.Storage && sa.cfg.Name != cfg.Name { - reservation += (int64(sa.cfg.Replicas) * sa.cfg.MaxBytes) - } - } + var reservation int64 + for _, sa := range jsa.streams { + // Don't count the stream toward the limit if it already exists. + if sa.cfg.Name == cfg.Name { + continue } - } else { - for _, sa := range jsa.streams { - if sa.cfg.Replicas == cfg.Replicas { - if sa.cfg.MaxBytes > 0 { - if isSameTier(&sa.cfg, cfg) && sa.cfg.Name != cfg.Name { - reservation += (int64(sa.cfg.Replicas) * sa.cfg.MaxBytes) - } - } + if (tier == _EMPTY_ || isSameTier(&sa.cfg, cfg)) && sa.cfg.MaxBytes > 0 && sa.cfg.Storage == cfg.Storage { + // If tier is empty, all storage is flat and we should adjust for replicas. + // Otherwise if tiered, storage replication already taken into consideration. + if tier == _EMPTY_ && sa.cfg.Replicas > 1 { + reservation = addSaturate(reservation, mulSaturate(int64(sa.cfg.Replicas), sa.cfg.MaxBytes)) + } else { + reservation = addSaturate(reservation, sa.cfg.MaxBytes) } } } @@ -1877,7 +1880,7 @@ func (s *Server) jsStreamNamesRequest(sub *subscription, c *client, _ *Account, s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } - offset = req.Offset + offset = max(req.Offset, 0) if req.Subject != _EMPTY_ { filter = req.Subject } @@ -2013,7 +2016,7 @@ func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, s s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } - offset = req.Offset + offset = max(req.Offset, 0) if req.Subject != _EMPTY_ { filter = req.Subject } @@ -2209,7 +2212,7 @@ func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, s return } details, subjects = req.DeletedDetails, req.SubjectsFilter - offset = req.Offset + offset = max(req.Offset, 0) } mset, err := acc.lookupStream(streamName) @@ -2576,7 +2579,7 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco } js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, name) + isLeader, sa := cc.isLeader(), js.streamAssignmentOrInflight(acc.Name, name) js.mu.RUnlock() // Make sure we are meta leader. @@ -2622,13 +2625,17 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco return } - // Check to see if we are a member of the group and if the group has no leader. - // Peers here is a server name, convert to node name. - nodeName := getHash(req.Peer) - js.mu.RLock() rg := sa.Group + + // Check to see if we are a member of the group. + // Peer here is either a peer ID or a server name, convert to node name. + nodeName := getHash(req.Peer) isMember := rg.isMember(nodeName) + if !isMember { + nodeName = req.Peer + isMember = rg.isMember(nodeName) + } js.mu.RUnlock() // Make sure we are a member. @@ -3083,6 +3090,13 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac var resp = JSApiAccountPurgeResponse{ApiResponse: ApiResponse{Type: JSApiAccountPurgeResponseType}} + // Check for path like separators in the name. + if strings.ContainsAny(accName, `\/`) { + resp.Error = NewJSStreamGeneralError(errors.New("account name can not contain path separators")) + s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) + return + } + if !s.JetStreamIsClustered() { var streams []*stream var ac *Account @@ -3135,22 +3149,23 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac return } - js.mu.RLock() + js.mu.Lock() ns, nc := 0, 0 - streams, hasAccount := cc.streams[accName] - for _, osa := range streams { - for _, oca := range osa.consumers { - oca.deleted = true + for osa := range js.streamAssignmentsOrInflightSeq(accName) { + for oca := range js.consumerAssignmentsOrInflightSeq(accName, osa.Config.Name) { ca := &consumerAssignment{Group: oca.Group, Stream: oca.Stream, Name: oca.Name, Config: oca.Config, Subject: subject, Client: oca.Client, Created: oca.Created} meta.Propose(encodeDeleteConsumerAssignment(ca)) + cc.trackInflightConsumerProposal(accName, osa.Config.Name, ca, true) nc++ } sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Client: osa.Client, Created: osa.Created} meta.Propose(encodeDeleteStreamAssignment(sa)) + cc.trackInflightStreamProposal(accName, sa, true) ns++ } - js.mu.RUnlock() + js.mu.Unlock() + hasAccount := ns > 0 s.Noticef("Purge request for account %s (streams: %d, consumer: %d, hasAccount: %t)", accName, ns, nc, hasAccount) resp.Initiated = true @@ -3830,9 +3845,10 @@ func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account } o.mu.Lock() - o.currentPinId = _EMPTY_ + o.unassignPinId() o.sendUnpinnedAdvisoryLocked(req.Group, "admin") o.mu.Unlock() + o.signalNewMessages() s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) } @@ -4026,11 +4042,8 @@ func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account if stream != req.Config.Name && req.Config.Name == _EMPTY_ { req.Config.Name = stream } - - // check stream config at the start of the restore process, not at the end - cfg, apiErr := s.checkStreamCfg(&req.Config, acc, false) - if apiErr != nil { - resp.Error = apiErr + if stream != req.Config.Name { + resp.Error = NewJSStreamMismatchError() s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } @@ -4040,6 +4053,14 @@ func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account return } + // check stream config at the start of the restore process, not at the end + cfg, apiErr := s.checkStreamCfg(&req.Config, acc, false) + if apiErr != nil { + resp.Error = apiErr + s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) + return + } + if err := acc.jsNonClusteredStreamLimitsCheck(&cfg); err != nil { resp.Error = err s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) @@ -4060,30 +4081,12 @@ func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account return } - s.processStreamRestore(ci, acc, &req.Config, subject, reply, string(msg)) + s.processStreamRestore(ci, acc, &cfg, subject, reply, string(msg)) } func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamConfig, subject, reply, msg string) <-chan error { - js := s.getJetStream() - var resp = JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} - snapDir := filepath.Join(js.config.StoreDir, snapStagingDir) - if _, err := os.Stat(snapDir); os.IsNotExist(err) { - if err := os.MkdirAll(snapDir, defaultDirPerms); err != nil { - resp.Error = &ApiError{Code: 503, Description: "JetStream unable to create temp storage for restore"} - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return nil - } - } - - tfile, err := os.CreateTemp(snapDir, "js-restore-") - if err != nil { - resp.Error = NewJSTempStorageFailedError() - s.sendAPIErrResponse(ci, acc, subject, reply, msg, s.jsonResponse(&resp)) - return nil - } - streamName := cfg.Name s.Noticef("Starting restore for stream '%s > %s'", acc.Name, streamName) @@ -4109,29 +4112,59 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC } // For signaling to upper layers. + var resultOnce sync.Once + var closeOnce sync.Once resultCh := make(chan result, 1) - activeQ := newIPQueue[int](s, fmt.Sprintf("[ACC:%s] stream '%s' restore", acc.Name, streamName)) // of int + pr, pw := io.Pipe() + + setResult := func(err error, reply string) { + resultOnce.Do(func() { + resultCh <- result{err, reply} + }) + } + activeQ := newIPQueue[int](s, fmt.Sprintf("[ACC:%s] stream '%s' restore", acc.Name, streamName)) + restoreCh := make(chan struct { + mset *stream + err error + }, 1) + closeWithError := func(err error) { + closeOnce.Do(func() { + if err != nil { + pw.CloseWithError(err) + } else { + pw.Close() + } + }) + } - var total int + s.startGoRoutine(func() { + defer s.grWG.Done() + mset, err := acc.RestoreStream(cfg, pr) + if err != nil { + pr.CloseWithError(err) + } else { + pr.Close() + } + restoreCh <- struct { + mset *stream + err error + }{ + mset: mset, + err: err, + } + }) - // FIXME(dlc) - Probably take out of network path eventually due to disk I/O? processChunk := func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { // We require reply subjects to communicate back failures, flow etc. If they do not have one log and cancel. if reply == _EMPTY_ { sub.client.processUnsub(sub.sid) - resultCh <- result{ - fmt.Errorf("restore for stream '%s > %s' requires reply subject for each chunk", acc.Name, streamName), - reply, - } + setResult(fmt.Errorf("restore for stream '%s > %s' requires reply subject for each chunk", acc.Name, streamName), reply) return } // Account client messages have \r\n on end. This is an error. if len(msg) < LEN_CR_LF { sub.client.processUnsub(sub.sid) - resultCh <- result{ - fmt.Errorf("restore for stream '%s > %s' received short chunk", acc.Name, streamName), - reply, - } + setResult(fmt.Errorf("restore for stream '%s > %s' received short chunk", acc.Name, streamName), reply) return } // Adjust. @@ -4139,26 +4172,32 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC // This means we are complete with our transfer from the client. if len(msg) == 0 { - s.Debugf("Finished staging restore for stream '%s > %s'", acc.Name, streamName) - resultCh <- result{err, reply} + s.Debugf("Finished streaming restore for stream '%s > %s'", acc.Name, streamName) + closeWithError(nil) + setResult(nil, reply) return } - // We track total and check on server limits. - // TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit. - total += len(msg) - if js.wouldExceedLimits(FileStorage, total) { - s.resourcesExceededError(FileStorage) - resultCh <- result{NewJSInsufficientResourcesError(), reply} - return - } + // Signal activity before and after the blocking write. + // The pre-write signal refreshes the stall watchdog when the + // chunk arrives; the post-write signal refreshes it again once + // RestoreStream has consumed the data. This keeps the idle + // window between chunks anchored to the end of the previous + // write instead of its start. + activeQ.push(0) - // Append chunk to temp file. Mark as issue if we encounter an error. - if n, err := tfile.Write(msg); n != len(msg) || err != nil { - resultCh <- result{err, reply} - if reply != _EMPTY_ { - s.sendInternalAccountMsg(acc, reply, "-ERR 'storage failure during restore'") + if _, err := pw.Write(msg); err != nil { + closeWithError(err) + sub.client.processUnsub(sub.sid) + var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} + if IsNatsErr(err, JSStorageResourcesExceededErr, JSMemoryResourcesExceededErr) { + s.resourcesExceededError(cfg.Storage) } + resp.Error = NewJSStreamRestoreError(err, Unless(err)) + if s.sendInternalAccountMsg(acc, reply, s.jsonResponse(&resp)) == nil { + reply = _EMPTY_ + } + setResult(err, reply) return } @@ -4169,8 +4208,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC sub, err := acc.subscribeInternal(restoreSubj, processChunk) if err != nil { - tfile.Close() - os.Remove(tfile.Name()) + closeWithError(err) resp.Error = NewJSRestoreSubscribeFailedError(err, restoreSubj) s.sendAPIErrResponse(ci, acc, subject, reply, msg, s.jsonResponse(&resp)) return nil @@ -4180,14 +4218,14 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC resp.DeliverSubject = restoreSubj s.sendAPIResponse(ci, acc, subject, reply, msg, s.jsonResponse(resp)) + // Returned to the caller to wait for completion. doneCh := make(chan error, 1) // Monitor the progress from another Go routine. s.startGoRoutine(func() { defer s.grWG.Done() defer func() { - tfile.Close() - os.Remove(tfile.Name()) + closeWithError(ErrConnectionClosed) sub.client.processUnsub(sub.sid) activeQ.unregister() }() @@ -4197,71 +4235,97 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC defer notActive.Stop() total := 0 + var inputDone bool + var replySubj string + var inputErr error + var restoreDone bool + var restoreResult struct { + mset *stream + err error + } + + finish := func(reply string, err error, mset *stream) { + end := time.Now().UTC() + + s.publishAdvisory(acc, JSAdvisoryStreamRestoreCompletePre+"."+streamName, &JSRestoreCompleteAdvisory{ + TypedEvent: TypedEvent{ + Type: JSRestoreCompleteAdvisoryType, + ID: nuid.Next(), + Time: end, + }, + Stream: streamName, + Start: start, + End: end, + Bytes: int64(total), + Client: ci.forAdvisory(), + Domain: domain, + }) + + var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} + if err != nil { + if IsNatsErr(err, JSStorageResourcesExceededErr, JSMemoryResourcesExceededErr) { + s.resourcesExceededError(cfg.Storage) + } + resp.Error = NewJSStreamRestoreError(err, Unless(err)) + s.Warnf("Restore failed for %s for stream '%s > %s' in %v", + friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start)) + } else { + msetCfg := mset.config() + resp.StreamInfo = &StreamInfo{ + Created: mset.createdTime(), + State: mset.state(), + Config: *setDynamicStreamMetadata(&msetCfg), + TimeStamp: time.Now().UTC(), + } + s.Noticef("Completed restore of %s for stream '%s > %s' in %v", + friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start).Round(time.Millisecond)) + } + if reply != _EMPTY_ { + s.sendInternalAccountMsg(acc, reply, s.jsonResponse(&resp)) + } + doneCh <- err + } + for { select { case result := <-resultCh: - err := result.err - var mset *stream - - // If we staged properly go ahead and do restore now. - if err == nil { - s.Debugf("Finalizing restore for stream '%s > %s'", acc.Name, streamName) - tfile.Seek(0, 0) - mset, err = acc.RestoreStream(cfg, tfile) - } else { - errStr := err.Error() - tmp := []rune(errStr) - tmp[0] = unicode.ToUpper(tmp[0]) - s.Warnf(errStr) + replySubj = result.reply + inputDone = true + inputErr = result.err + notActive.Stop() + if result.err != nil { + closeWithError(result.err) + s.Warnf(result.err.Error()) } - - end := time.Now().UTC() - - // TODO(rip) - Should this have the error code in it?? - s.publishAdvisory(acc, JSAdvisoryStreamRestoreCompletePre+"."+streamName, &JSRestoreCompleteAdvisory{ - TypedEvent: TypedEvent{ - Type: JSRestoreCompleteAdvisoryType, - ID: nuid.Next(), - Time: end, - }, - Stream: streamName, - Start: start, - End: end, - Bytes: int64(total), - Client: ci.forAdvisory(), - Domain: domain, - }) - - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - - if err != nil { - resp.Error = NewJSStreamRestoreError(err, Unless(err)) - s.Warnf("Restore failed for %s for stream '%s > %s' in %v", - friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start)) - } else { - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - TimeStamp: time.Now().UTC(), + if restoreDone { + err := inputErr + if err == nil { + err = restoreResult.err } - s.Noticef("Completed restore of %s for stream '%s > %s' in %v", - friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start).Round(time.Millisecond)) + finish(replySubj, err, restoreResult.mset) + return + } + case rr := <-restoreCh: + restoreDone = true + restoreResult = rr + if inputDone { + err := inputErr + if err == nil { + err = rr.err + } + finish(replySubj, err, rr.mset) + return } - - // On the last EOF, send back the stream info or error status. - s.sendInternalAccountMsg(acc, result.reply, s.jsonResponse(&resp)) - // Signal to the upper layers. - doneCh <- err - return case <-activeQ.ch: if n, ok := activeQ.popOne(); ok { total += n - notActive.Reset(activityInterval) + if !inputDone { + notActive.Reset(activityInterval) + } } case <-notActive.C: - err := fmt.Errorf("restore for stream '%s > %s' is stalled", acc, streamName) + err := fmt.Errorf("restore for stream '%s > %s' is stalled", acc.Name, streamName) + closeWithError(err) doneCh <- err return } @@ -4398,20 +4462,36 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun } // Default chunk size for now. -const defaultSnapshotChunkSize = 128 * 1024 -const defaultSnapshotWindowSize = 8 * 1024 * 1024 // 8MB +const defaultSnapshotChunkSize = 128 * 1024 // 128KiB +const defaultSnapshotWindowSize = 8 * 1024 * 1024 // 8MiB +const defaultSnapshotAckTimeout = 5 * time.Second + +var snapshotAckTimeout = defaultSnapshotAckTimeout // streamSnapshot will stream out our snapshot to the reply subject. func (s *Server) streamSnapshot(acc *Account, mset *stream, sr *SnapshotResult, req *JSApiStreamSnapshotRequest) { - chunkSize := req.ChunkSize + chunkSize, wndSize := req.ChunkSize, req.WindowSize if chunkSize == 0 { chunkSize = defaultSnapshotChunkSize } + if wndSize == 0 { + wndSize = defaultSnapshotWindowSize + } + chunkSize = min(max(1024, chunkSize), 1024*1024) // Clamp within 1KiB to 1MiB + wndSize = min(max(1024, wndSize), 32*1024*1024) // Clamp within 1KiB to 32MiB + wndSize = max(wndSize, chunkSize) // Guarantee at least one chunk + maxInflight := wndSize / chunkSize // Between 1 and 32,768 + // Setup for the chunk stream. reply := req.DeliverSubject r := sr.Reader defer r.Close() + // In case we run into an error, this allows subscription callbacks + // to not sit and block endlessly. + done := make(chan struct{}) + defer close(done) + // Check interest for the snapshot deliver subject. inch := make(chan bool, 1) acc.sl.RegisterNotification(req.DeliverSubject, inch) @@ -4425,78 +4505,59 @@ func (s *Server) streamSnapshot(acc *Account, mset *stream, sr *SnapshotResult, } } - // Create our ack flow handler. - // This is very simple for now. - ackSize := defaultSnapshotWindowSize / chunkSize - if ackSize < 8 { - ackSize = 8 - } else if ackSize > 8*1024 { - ackSize = 8 * 1024 + // One slot per chunk. Each chunk read takes a slot, each ack will + // replace it. Smooths out in-flight number of chunks. + slots := make(chan struct{}, maxInflight) + for range maxInflight { + slots <- struct{}{} } - acks := make(chan struct{}, ackSize) - acks <- struct{}{} - - // Track bytes outstanding. - var out int32 // We will place sequence number and size of chunk sent in the reply. ackSubj := fmt.Sprintf(jsSnapshotAckT, mset.name(), nuid.Next()) ackSub, _ := mset.subscribeInternal(ackSubj+".>", func(_ *subscription, _ *client, _ *Account, subject, _ string, _ []byte) { - cs, _ := strconv.Atoi(tokenAt(subject, 6)) - // This is very crude and simple, but ok for now. - // This only matters when sending multiple chunks. - if atomic.AddInt32(&out, int32(-cs)) < defaultSnapshotWindowSize { - select { - case acks <- struct{}{}: - default: - } + select { + case slots <- struct{}{}: + case <-done: } }) defer mset.unsubscribe(ackSub) - // TODO(dlc) - Add in NATS-Chunked-Sequence header var hdr []byte + chunk := make([]byte, chunkSize) for index := 1; ; index++ { - chunk := make([]byte, chunkSize) - n, err := r.Read(chunk) - chunk = chunk[:n] + select { + case <-slots: + // A slot has become available. + case <-inch: + // The receiver appears to have gone away. + hdr = []byte("NATS/1.0 408 No Interest\r\n\r\n") + goto done + case err := <-sr.errCh: + // The snapshotting goroutine has failed for some reason. + hdr = []byte(fmt.Sprintf("NATS/1.0 500 %s\r\n\r\n", err)) + goto done + case <-time.After(snapshotAckTimeout): + // It's taking a very long time for the receiver to send us acks, + // they have probably stalled or there is high loss on the link. + hdr = []byte("NATS/1.0 408 No Flow Response\r\n\r\n") + goto done + } + n, err := io.ReadFull(r, chunk) + chunk := chunk[:n] if err != nil { if n > 0 { mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, chunk, nil, 0)) } break } - - // Wait on acks for flow control if past our window size. - // Wait up to 10ms for now if no acks received. - if atomic.LoadInt32(&out) > defaultSnapshotWindowSize { - select { - case <-acks: - // ok to proceed. - case <-inch: - // Lost interest - hdr = []byte("NATS/1.0 408 No Interest\r\n\r\n") - goto done - case <-time.After(2 * time.Second): - hdr = []byte("NATS/1.0 408 No Flow Response\r\n\r\n") - goto done - } - } ackReply := fmt.Sprintf("%s.%d.%d", ackSubj, len(chunk), index) if hdr == nil { hdr = []byte("NATS/1.0 204\r\n\r\n") } mset.outq.send(newJSPubMsg(reply, _EMPTY_, ackReply, nil, chunk, nil, 0)) - atomic.AddInt32(&out, int32(len(chunk))) - } - - if err := <-sr.errCh; err != _EMPTY_ { - hdr = []byte(fmt.Sprintf("NATS/1.0 500 %s\r\n\r\n", err)) } done: - // Send last EOF - // TODO(dlc) - place hash in header mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) } @@ -4639,6 +4700,8 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } + // Durable, so we need to honor the name. + req.Config.Name = consumerName } // If new style and durable set make sure they match. if rt == ccNew { @@ -4790,7 +4853,7 @@ func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } - offset = req.Offset + offset = max(req.Offset, 0) } streamName := streamNameFromSubject(subject) @@ -4918,7 +4981,7 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account, s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) return } - offset = req.Offset + offset = max(req.Offset, 0) } streamName := streamNameFromSubject(subject) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go index 89966b43a65..ea524abe7ef 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "iter" "math" "math/rand" "os" @@ -29,6 +30,7 @@ import ( "slices" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -47,10 +49,11 @@ type jetStreamCluster struct { streams map[string]map[string]*streamAssignment // These are inflight proposals and used to apply limits when there are // concurrent requests that would otherwise be accepted. - // We also record the group for the stream. This is needed since if we have - // concurrent requests for same account and stream we need to let it process to get + // We also record the assignment for the stream/consumer. This is needed since if we have + // concurrent requests for same account and stream/consumer we need to let it process to get // a response but they need to be same group, peers etc. and sync subjects. - inflight map[string]map[string]*inflightInfo + inflightStreams map[string]map[string]*inflightStreamInfo + inflightConsumers map[string]map[string]map[string]*inflightConsumerInfo // Holds a map of a peer ID to the reply subject, to only respond after gaining // quorum on the peer-remove action. peerRemoveReply map[string]peerRemoveInfo @@ -80,11 +83,18 @@ type jetStreamCluster struct { lastMetaSnapDuration int64 // Duration in nanoseconds } -// Used to track inflight stream add requests to properly re-use same group and sync subject. -type inflightInfo struct { - rg *raftGroup - sync string - cfg *StreamConfig +// Used to track inflight stream create/update/delete requests that have been proposed but not yet applied. +type inflightStreamInfo struct { + ops uint64 // Inflight operations, i.e. inflight stream creates/updates/deletes. + deleted bool // Whether the stream has been deleted. + *streamAssignment +} + +// Used to track inflight consumer create/update/delete requests that have been proposed but not yet applied. +type inflightConsumerInfo struct { + ops uint64 // Inflight operations, i.e. inflight consumer creates/updates/deletes. + deleted bool // Whether the consumer has been deleted. + *consumerAssignment } // Used to track inflight peer-remove info to respond 'success' after quorum. @@ -249,8 +259,6 @@ type consumerAssignment struct { // Internal responded bool recovering bool - pending bool - deleted bool err error unsupported *unsupportedConsumerAssignment } @@ -326,7 +334,6 @@ type writeableConsumerAssignment struct { Stream string `json:"stream"` ConfigJSON json.RawMessage `json:"consumer"` Group *raftGroup `json:"group"` - State *ConsumerState `json:"state,omitempty"` } // streamPurge is what the stream leader will replicate when purging a stream. @@ -432,12 +439,12 @@ func (s *Server) JetStreamSnapshotMeta() error { return errNotLeader } - snap, err := js.metaSnapshot() + snap, _, _, err := js.metaSnapshot() if err != nil { return err } - return meta.InstallSnapshot(snap) + return meta.InstallSnapshot(snap, false) } func (s *Server) JetStreamStepdownStream(account, stream string) error { @@ -522,7 +529,7 @@ func (s *Server) JetStreamSnapshotStream(account, stream string) error { mset.mu.Unlock() return nil } - err = mset.node.InstallSnapshot(mset.stateSnapshotLocked()) + err = mset.node.InstallSnapshot(mset.stateSnapshotLocked(), false) mset.mu.Unlock() return err @@ -689,10 +696,6 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum js.mu.RUnlock() return errors.New("consumer assignment or group missing") } - if ca.deleted { - js.mu.RUnlock() - return nil // No further checks, consumer was deleted in the meantime. - } created := ca.Created node := ca.Group.node js.mu.RUnlock() @@ -742,11 +745,10 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum // subjectsOverlap checks all existing stream assignments for the account cross-cluster for subject overlap // Use only for clustered JetStream // Read lock should be held. -func (jsc *jetStreamCluster) subjectsOverlap(acc string, subjects []string, osa *streamAssignment) bool { - asa := jsc.streams[acc] - for _, sa := range asa { +func (js *jetStream) subjectsOverlap(acc string, subjects []string, osa *streamAssignment) bool { + for sa := range js.streamAssignmentsOrInflightSeq(acc) { // can't overlap yourself, assume osa pre-checked for deep equal if passed - if osa != nil && sa == osa { + if osa != nil && sa.Config.Name == osa.Config.Name { continue } for _, subj := range sa.Config.Subjects { @@ -1182,18 +1184,95 @@ func (cc *jetStreamCluster) isConsumerLeader(account, stream, consumer string) b return false } -// Remove the stream `streamName` for the account `accName` from the inflight -// proposals map. This is done on success (processStreamAssignment) or on -// failure (processStreamAssignmentResults). +// Track the stream for the account `accName` in the inflight proposals map. +// This is done after proposing a stream change. // (Write) Lock held on entry. -func (cc *jetStreamCluster) removeInflightProposal(accName, streamName string) { - streams, ok := cc.inflight[accName] +func (cc *jetStreamCluster) trackInflightStreamProposal(accName string, sa *streamAssignment, deleted bool) { + if cc.inflightStreams == nil { + cc.inflightStreams = make(map[string]map[string]*inflightStreamInfo) + } + streams, ok := cc.inflightStreams[accName] if !ok { - return + streams = make(map[string]*inflightStreamInfo) + cc.inflightStreams[accName] = streams } - delete(streams, streamName) - if len(streams) == 0 { - delete(cc.inflight, accName) + if inflight, ok := streams[sa.Config.Name]; ok { + inflight.ops++ + inflight.deleted = deleted + inflight.streamAssignment = sa + } else { + streams[sa.Config.Name] = &inflightStreamInfo{1, deleted, sa} + } +} + +// Remove the stream `streamName` for the account `accName` from the inflight proposals map. +// This is done after successful create/update/delete. +// (Write) Lock held on entry. +func (cc *jetStreamCluster) removeInflightStreamProposal(accName, streamName string) { + if streams, ok := cc.inflightStreams[accName]; !ok { + return // No accounts. + } else if inflight, ok := streams[streamName]; !ok { + return // No changes for this stream. + } else if inflight.ops > 1 { + // Decrement one pending operation. + inflight.ops-- + } else { + // No pending operations left, clean up. + delete(streams, streamName) + if len(streams) == 0 { + delete(cc.inflightStreams, accName) + } + } +} + +// Track the consumer for the `streamName` and account `accName` in the inflight proposals map. +// This is done after proposing a consumer change. +// (Write) Lock held on entry. +func (cc *jetStreamCluster) trackInflightConsumerProposal(accName, streamName string, ca *consumerAssignment, deleted bool) { + if cc.inflightConsumers == nil { + cc.inflightConsumers = make(map[string]map[string]map[string]*inflightConsumerInfo) + } + streams, ok := cc.inflightConsumers[accName] + if !ok { + streams = make(map[string]map[string]*inflightConsumerInfo) + cc.inflightConsumers[accName] = streams + } + consumers, ok := streams[streamName] + if !ok { + consumers = make(map[string]*inflightConsumerInfo) + streams[streamName] = consumers + } + if inflight, ok := consumers[ca.Name]; ok { + inflight.ops++ + inflight.deleted = deleted + inflight.consumerAssignment = ca + } else { + consumers[ca.Name] = &inflightConsumerInfo{1, deleted, ca} + } +} + +// Remove the consumer `consumerName` for the `streamName` and account `accName` from the inflight proposals map. +// This is done after successful create/update/delete. +// (Write) Lock held on entry. +func (cc *jetStreamCluster) removeInflightConsumerProposal(accName, streamName, consumerName string) { + if streams, ok := cc.inflightConsumers[accName]; !ok { + return // No accounts. + } else if consumers, ok := streams[streamName]; !ok { + return // No streams. + } else if inflight, ok := consumers[consumerName]; !ok { + return // No changes for this consumer. + } else if inflight.ops > 1 { + // Decrement one pending operation. + inflight.ops-- + } else { + // No pending operations left, clean up. + delete(consumers, consumerName) + if len(consumers) == 0 { + delete(streams, streamName) + } + if len(streams) == 0 { + delete(cc.inflightConsumers, accName) + } } } @@ -1311,27 +1390,7 @@ func (js *jetStream) checkForOrphans() { s.Debugf("JetStream cluster skipping check for orphans, not current with the meta-leader") return } - - var streams []*stream - var consumers []*consumer - - for accName, jsa := range js.accounts { - asa := cc.streams[accName] - jsa.mu.RLock() - for stream, mset := range jsa.streams { - if sa := asa[stream]; sa == nil { - streams = append(streams, mset) - } else { - // This one is good, check consumers now. - for _, o := range mset.getConsumers() { - if sa.consumers[o.String()] == nil { - consumers = append(consumers, o) - } - } - } - } - jsa.mu.RUnlock() - } + streams, consumers := js.getOrphans() js.mu.Unlock() for _, mset := range streams { @@ -1365,6 +1424,31 @@ func (js *jetStream) checkForOrphans() { } } +// Returns orphaned streams and consumers that were recovered from disk, but don't +// exist as clustered stream/consumer assignments. +// Lock should be held. +func (js *jetStream) getOrphans() (streams []*stream, consumers []*consumer) { + cc := js.cluster + for accName, jsa := range js.accounts { + asa := cc.streams[accName] + jsa.mu.RLock() + for stream, mset := range jsa.streams { + if sa := asa[stream]; sa == nil { + streams = append(streams, mset) + } else { + // This one is good, check consumers now. + for _, o := range mset.getPublicConsumers() { + if sa.consumers[o.String()] == nil { + consumers = append(consumers, o) + } + } + } + } + jsa.mu.RUnlock() + } + return streams, consumers +} + func (js *jetStream) monitorCluster() { s, n := js.server(), js.getMetaGroup() qch, stopped, rqch, lch, aq := js.clusterQuitC(), js.clusterStoppedC(), n.QuitC(), n.LeadChangeC(), n.ApplyQ() @@ -1380,6 +1464,7 @@ func (js *jetStream) monitorCluster() { defer s.isMetaLeader.Store(false) const compactInterval = time.Minute + const compactMinInterval = 15 * time.Second t := time.NewTicker(compactInterval) defer t.Stop() @@ -1416,30 +1501,177 @@ func (js *jetStream) monitorCluster() { recovering := true // Snapshotting function. + var ( + snapMu sync.Mutex + snapshotting bool + // The first snapshot we do after recovery should clean up most of the log. + fallbackSnapshot = true + // Suppress non-forced snapshots after an error. Timer-based snapshots will become forced. + // If failures continue, we force a snapshot even if we were catching up others + // (otherwise we might indefinitely stall and grow log size). + failedSnapshots atomic.Uint32 + ) doSnapshot := func(force bool) { // Suppress during recovery. - if recovering { + // If snapshots have failed, and we're not forced to, we'll wait for the timer since it'll now be forced. + if recovering || (!force && failedSnapshots.Load() > 0) { + return + } + // Suppress if an async snapshot is already in progress. + snapMu.Lock() + if snapshotting { + snapMu.Unlock() return } // Look up what the threshold is for compaction. Re-reading from config here as it is reloadable. js.srv.optsMu.RLock() ethresh := js.srv.opts.JetStreamMetaCompact szthresh := js.srv.opts.JetStreamMetaCompactSize + // Allows reverting to sync/blocking snapshots instead of the default async snapshots. + syncSnapshot := js.srv.opts.JetStreamMetaCompactSync js.srv.optsMu.RUnlock() // Work out our criteria for snapshotting. byEntries, bySize := ethresh > 0, szthresh > 0 byNeither := !byEntries && !bySize // For the meta layer we want to snapshot when over the above threshold (which could be 0 by default). - if ne, nsz := n.Size(); force || byNeither || (byEntries && ne > ethresh) || (bySize && nsz > szthresh) || n.NeedSnapshot() { - snap, err := js.metaSnapshot() + ne, nsz := n.Size() + createSnapshot := force || byNeither || (byEntries && ne > ethresh) || (bySize && nsz > szthresh) || n.NeedSnapshot() + if !createSnapshot { + snapMu.Unlock() + return + } + + // Start a checkpoint, we can either install the snapshot sync or async from here. + // If we had a significant number of failed snapshots, start relaxing Raft-layer checks + // to force it through. We might have been catching up a peer for a long period, and this + // protects our log size from growing indefinitely. + forceSnapshot := failedSnapshots.Load() > 4 + c, err := n.CreateSnapshotCheckpoint(forceSnapshot) + if err != nil { + if err != errNoSnapAvailable && err != errNodeClosed { + s.Warnf("Error snapshotting JetStream cluster state: %v", err) + // If this is the first failure, reduce the interval of the snapshot timer. + // This ensures we're not waiting too long for snapshotting to eventually become forced. + if failedSnapshots.Load() == 0 { + t.Reset(compactMinInterval) + } + failedSnapshots.Add(1) + } + snapMu.Unlock() + return + } + + // If we're meant to install an async snapshot, check if the underlying log has grown way too large. + // In that case, we fall back to a synchronous/blocking snapshot after all. This is a protective + // measure to prevent the log from growing faster than we can asynchronously compact. + if !fallbackSnapshot && !syncSnapshot { + if szthresh == 0 { + szthresh = compactSizeMin + } + if thresh := 10 * szthresh; nsz >= thresh { + s.rateLimitFormatWarnf("JetStream cluster metalayer log size has exceeded async threshold (%s), will fall back to blocking snapshot", friendlyBytes(thresh)) + fallbackSnapshot = true + } + } + + // Check if we should fall back to a blocking snapshot, either due to failures or extreme log size. + if fallbackSnapshot || syncSnapshot { + if !syncSnapshot { + s.rateLimitFormatWarnf("Metalayer blocking snapshot starting") + } + start := time.Now() + snap, nsa, nca, err := js.metaSnapshot() if err != nil { s.Warnf("Error generating JetStream cluster snapshot: %v", err) - } else if err = n.InstallSnapshot(snap); err == nil { + c.Abort() + } else if csz, err := c.InstallSnapshot(snap); err == nil { lastSnapTime = time.Now() - } else if err != errNoSnapAvailable && err != errNodeClosed { - s.Warnf("Error snapshotting JetStream cluster state: %v", err) + // If there was a failed snapshot before, we reduced the timer's interval. + // Reset it back to the original interval now. + if failedSnapshots.Load() > 0 { + t.Reset(compactInterval) + } + failedSnapshots.Store(0) + // Fallback snapshot was successful, the next one can be async again. + fallbackSnapshot = false + took := time.Since(start) + if !syncSnapshot || took > 2*time.Second { + s.rateLimitFormatWarnf("Metalayer blocking snapshot took %.3fs (streams: %d, consumers: %d, compacted: %s)", + took.Seconds(), nsa, nca, friendlyBytes(csz)) + } + } else { + c.Abort() + if err != errNoSnapAvailable && err != errNodeClosed && err != errSnapAborted { + s.Warnf("Error snapshotting JetStream cluster state: %v", err) + // If this is the first failure, reduce the interval of the snapshot timer. + // This ensures we're not waiting too long for snapshotting to eventually become forced. + if failedSnapshots.Load() == 0 { + t.Reset(compactMinInterval) + } + failedSnapshots.Add(1) + } } + snapMu.Unlock() + return } + + // Perform the snapshot asynchronously. + start := time.Now() + snapshotting = true + snapMu.Unlock() + s.startGoRoutine(func() { + defer s.grWG.Done() + + abort := func(err error) { + snapMu.Lock() + c.Abort() + if err != errNoSnapAvailable && err != errNodeClosed && err != errSnapAborted { + s.Warnf("Error snapshotting JetStream cluster state: %v, will fall back to blocking snapshot", err) + fallbackSnapshot = true + // If this is the first failure, reduce the interval of the snapshot timer. + // This ensures we're not waiting too long for snapshotting to eventually become forced. + if failedSnapshots.Load() == 0 { + t.Reset(compactMinInterval) + } + failedSnapshots.Add(1) + } + snapshotting = false + snapMu.Unlock() + } + + // The strategy of asynchronous snapshotting: + // - Not holding any locks. Only minimal for the Raft node itself, importantly not the JS lock. + // - Load the last snapshot. + // - Replay stream/consumer changes through the assignment state machine. + // - Encode and install as the new snapshot. + if data, err := c.LoadLastSnapshot(); err != nil && err != errNoSnapAvailable { + abort(err) + } else if streams, err := js.decodeMetaSnapshot(data); err != nil { + abort(err) + } else if err = js.collectStreamAndConsumerChanges(c, streams); err != nil { + abort(err) + } else if snap, nsa, nca, err := js.encodeMetaSnapshot(streams); err != nil { + abort(err) + } else if csz, err := c.InstallSnapshot(snap); err != nil { + abort(err) + } else { + // Successful snapshot. + lastSnapTime = time.Now() + // If there was a failed snapshot before, we reduced the timer's interval. + // Reset it back to the original interval now. + if failedSnapshots.Load() > 0 { + t.Reset(compactInterval) + } + failedSnapshots.Store(0) + if took := time.Since(start); took > 2*time.Second { + s.rateLimitFormatWarnf("Metalayer async snapshot took %.3fs (streams: %d, consumers: %d, compacted: %s)", + took.Seconds(), nsa, nca, friendlyBytes(csz)) + } + snapMu.Lock() + snapshotting = false + snapMu.Unlock() + } + }) } var ru *recoveryUpdates @@ -1453,6 +1685,9 @@ func (js *jetStream) monitorCluster() { select { case <-s.quitCh: // Server shutting down, but we might receive this before qch, so try to snapshot. + snapMu.Lock() + fallbackSnapshot = true + snapMu.Unlock() doSnapshot(false) return case <-rqch: @@ -1460,6 +1695,9 @@ func (js *jetStream) monitorCluster() { return case <-qch: // Clean signal from shutdown routine so do best effort attempt to snapshot meta layer. + snapMu.Lock() + fallbackSnapshot = true + snapMu.Unlock() doSnapshot(false) return case <-aq.ch: @@ -1510,6 +1748,11 @@ func (js *jetStream) monitorCluster() { // Snapshot now so we start with freshly compacted log. doSnapshot(true) if wasMetaRecovering { + // Reset, it could be we didn't need to install a snapshot. This ensures we don't degrade + // to a blocking snapshot if we install our first snapshot during normal operations. + snapMu.Lock() + fallbackSnapshot = false + snapMu.Unlock() oc = time.AfterFunc(30*time.Second, js.checkForOrphans) // Do a health check here as well. go checkHealth() @@ -1546,7 +1789,9 @@ func (js *jetStream) monitorCluster() { } case <-t.C: - doSnapshot(false) + // Start forcing snapshots if they failed previously. + forceIfFailed := failedSnapshots.Load() > 0 + doSnapshot(forceIfFailed) // Periodically check the cluster size. if n.Leader() { js.checkClusterSize() @@ -1639,121 +1884,17 @@ func (js *jetStream) clusterStreamConfig(accName, streamName string) (StreamConf return StreamConfig{}, false } -func (js *jetStream) metaSnapshot() ([]byte, error) { - start := time.Now() +func (js *jetStream) metaSnapshot() ([]byte, int, int, error) { js.mu.RLock() - s := js.srv + defer js.mu.RUnlock() cc := js.cluster - nsa := 0 - nca := 0 - for _, asa := range cc.streams { - nsa += len(asa) - } - streams := make([]writeableStreamAssignment, 0, nsa) - for _, asa := range cc.streams { - for _, sa := range asa { - wsa := writeableStreamAssignment{ - Client: sa.Client.forAssignmentSnap(), - Created: sa.Created, - ConfigJSON: sa.ConfigJSON, - Group: sa.Group, - Sync: sa.Sync, - Consumers: make([]*writeableConsumerAssignment, 0, len(sa.consumers)), - } - for _, ca := range sa.consumers { - // Skip if the consumer is pending, we can't include it in our snapshot. - // If the proposal fails after we marked it pending, it would result in a ghost consumer. - if ca.pending { - continue - } - wca := writeableConsumerAssignment{ - Client: ca.Client.forAssignmentSnap(), - Created: ca.Created, - Name: ca.Name, - Stream: ca.Stream, - ConfigJSON: ca.ConfigJSON, - Group: ca.Group, - State: ca.State, - } - wsa.Consumers = append(wsa.Consumers, &wca) - nca++ - } - streams = append(streams, wsa) - } - } - - if len(streams) == 0 { - js.mu.RUnlock() - return nil, nil - } - - // Track how long it took to marshal the JSON - mstart := time.Now() - b, err := json.Marshal(streams) - mend := time.Since(mstart) - - js.mu.RUnlock() - - // Must not be possible for a JSON marshaling error to result - // in an empty snapshot. - if err != nil { - return nil, err - } - - // Track how long it took to compress the JSON. - cstart := time.Now() - snap := s2.Encode(nil, b) - cend := time.Since(cstart) - took := time.Since(start) - - if took > time.Second { - s.rateLimitFormatWarnf("Metalayer snapshot took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %d, compressed: %d)", - took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), len(b), len(snap)) - } - - // Track in jsz monitoring as well. - if cc != nil { - atomic.StoreInt64(&cc.lastMetaSnapTime, start.UnixNano()) - atomic.StoreInt64(&cc.lastMetaSnapDuration, int64(took)) - } - - return snap, nil + return js.encodeMetaSnapshot(cc.streams) } -func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering, startupRecovery bool) error { - var wsas []writeableStreamAssignment - if len(buf) > 0 { - jse, err := s2.Decode(nil, buf) - if err != nil { - return err - } - if err = json.Unmarshal(jse, &wsas); err != nil { - return err - } - } - - // Build our new version here outside of js. - streams := make(map[string]map[string]*streamAssignment) - for _, wsa := range wsas { - as := streams[wsa.Client.serviceAccount()] - if as == nil { - as = make(map[string]*streamAssignment) - streams[wsa.Client.serviceAccount()] = as - } - sa := &streamAssignment{Client: wsa.Client, Created: wsa.Created, ConfigJSON: wsa.ConfigJSON, Group: wsa.Group, Sync: wsa.Sync} - decodeStreamAssignmentConfig(js.srv, sa) - if len(wsa.Consumers) > 0 { - sa.consumers = make(map[string]*consumerAssignment) - for _, wca := range wsa.Consumers { - if wca.Stream == _EMPTY_ { - wca.Stream = sa.Config.Name // Rehydrate from the stream name. - } - ca := &consumerAssignment{Client: wca.Client, Created: wca.Created, Name: wca.Name, Stream: wca.Stream, ConfigJSON: wca.ConfigJSON, Group: wca.Group, State: wca.State} - decodeConsumerAssignmentConfig(ca) - sa.consumers[ca.Name] = ca - } - } - as[sa.Config.Name] = sa +func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error { + streams, err := js.decodeMetaSnapshot(buf) + if err != nil { + return err } js.mu.Lock() @@ -1863,6 +2004,236 @@ func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecove } } + // If we're not recovering, we need to check if we have any left-over streams or consumers that aren't + // tracked in our assignments. This could happen if we've restarted and recovered streams or consumers + // from disk, but then got sent a snapshot to catch up from the meta-leader. + if !isRecovering { + // This logic is similar to that of checkForOrphans. But, this cleanup is focused on aligning with + // a snapshot from a meta leader as a result of catchup. We silently delete them here, instead of + // logging and sending out advisories. + js.mu.RLock() + deleteStreams, deleteConsumers := js.getOrphans() + js.mu.RUnlock() + for _, mset := range deleteStreams { + mset.stop(true, false) + } + for _, o := range deleteConsumers { + o.deleteWithoutAdvisory() + } + } + return nil +} + +// Decode the meta snapshot from buf into the relevant stream and consumer assignments. +func (js *jetStream) decodeMetaSnapshot(buf []byte) (map[string]map[string]*streamAssignment, error) { + var wsas []writeableStreamAssignment + if len(buf) > 0 { + jse, err := s2.Decode(nil, buf) + if err != nil { + return nil, err + } + if err = json.Unmarshal(jse, &wsas); err != nil { + return nil, err + } + } + + // Build our new version here outside of js. + streams := make(map[string]map[string]*streamAssignment) + for _, wsa := range wsas { + as := streams[wsa.Client.serviceAccount()] + if as == nil { + as = make(map[string]*streamAssignment) + streams[wsa.Client.serviceAccount()] = as + } + sa := &streamAssignment{Client: wsa.Client, Created: wsa.Created, ConfigJSON: wsa.ConfigJSON, Group: wsa.Group, Sync: wsa.Sync} + if err := decodeStreamAssignmentConfig(js.srv, sa); err != nil { + return nil, err + } + if len(wsa.Consumers) > 0 { + sa.consumers = make(map[string]*consumerAssignment) + for _, wca := range wsa.Consumers { + if wca.Stream == _EMPTY_ { + wca.Stream = sa.Config.Name // Rehydrate from the stream name. + } + ca := &consumerAssignment{Client: wca.Client, Created: wca.Created, Name: wca.Name, Stream: wca.Stream, ConfigJSON: wca.ConfigJSON, Group: wca.Group} + if err := decodeConsumerAssignmentConfig(ca); err != nil { + return nil, err + } + sa.consumers[ca.Name] = ca + } + } + as[sa.Config.Name] = sa + } + return streams, nil +} + +// Encode the meta assignments into an encoded and compressed buffer. +// Returns the snapshot itself, and the amount of streams and consumers. +func (js *jetStream) encodeMetaSnapshot(streams map[string]map[string]*streamAssignment) ([]byte, int, int, error) { + start := time.Now() + nsa := 0 + nca := 0 + for _, asa := range streams { + nsa += len(asa) + } + out := make([]writeableStreamAssignment, 0, nsa) + for _, asa := range streams { + for _, sa := range asa { + wsa := writeableStreamAssignment{ + Client: sa.Client.forAssignmentSnap(), + Created: sa.Created, + ConfigJSON: sa.ConfigJSON, + Group: sa.Group, + Sync: sa.Sync, + Consumers: make([]*writeableConsumerAssignment, 0, len(sa.consumers)), + } + for _, ca := range sa.consumers { + wca := writeableConsumerAssignment{ + Client: ca.Client.forAssignmentSnap(), + Created: ca.Created, + Name: ca.Name, + Stream: ca.Stream, + ConfigJSON: ca.ConfigJSON, + Group: ca.Group, + } + wsa.Consumers = append(wsa.Consumers, &wca) + nca++ + } + out = append(out, wsa) + } + } + + if len(out) == 0 { + return nil, nsa, nca, nil + } + + // Track how long it took to marshal the JSON + mstart := time.Now() + b, err := json.Marshal(out) + mend := time.Since(mstart) + + // Must not be possible for a JSON marshaling error to result + // in an empty snapshot. + if err != nil { + return nil, nsa, nca, err + } + + // Track how long it took to compress the JSON. + cstart := time.Now() + snap := s2.Encode(nil, b) + cend := time.Since(cstart) + took := time.Since(start) + + if took > 2*time.Second { + js.srv.rateLimitFormatWarnf("Metalayer snapshot generation took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %s, compressed: %s)", + took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), friendlyBytes(len(b)), friendlyBytes(len(snap))) + } + + // Track in jsz monitoring as well. + if cc := js.cluster; cc != nil { + atomic.StoreInt64(&cc.lastMetaSnapTime, start.UnixNano()) + atomic.StoreInt64(&cc.lastMetaSnapDuration, int64(took)) + } + + return snap, nsa, nca, nil +} + +// Given a checkpoint, collects all relevant append entries that can be snapshotted. +func (js *jetStream) collectStreamAndConsumerChanges(c RaftNodeCheckpoint, streams map[string]map[string]*streamAssignment) error { + ru := &recoveryUpdates{ + removeStreams: make(map[string]*streamAssignment), + removeConsumers: make(map[string]map[string]*consumerAssignment), + addStreams: make(map[string]*streamAssignment), + updateStreams: make(map[string]*streamAssignment), + updateConsumers: make(map[string]map[string]*consumerAssignment), + } + for ae, err := range c.AppendEntriesSeq() { + if err != nil { + return err + } + for _, e := range ae.entries { + if e.Type == EntryNormal { + buf := e.Data + op := entryOp(buf[0]) + switch op { + case assignStreamOp, updateStreamOp, removeStreamOp: + sa, err := decodeStreamAssignment(js.srv, buf[1:]) + if err != nil { + js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:]) + panic(err) + } + if op == removeStreamOp { + ru.removeStream(sa) + } else { + ru.addStream(sa) + } + case assignConsumerOp: + ca, err := decodeConsumerAssignment(buf[1:]) + if err != nil { + js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:]) + panic(err) + } + ru.addOrUpdateConsumer(ca) + case assignCompressedConsumerOp: + ca, err := decodeConsumerAssignmentCompressed(buf[1:]) + if err != nil { + js.srv.Errorf("JetStream cluster failed to decode compressed consumer assignment: %q", buf[1:]) + panic(err) + } + ru.addOrUpdateConsumer(ca) + case removeConsumerOp: + ca, err := decodeConsumerAssignment(buf[1:]) + if err != nil { + js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:]) + panic(err) + } + ru.removeConsumer(ca) + default: + panic(fmt.Sprintf("JetStream Cluster Unknown meta entry op type: %v", entryOp(buf[0]))) + } + } + } + } + + // With our recovery structure, apply the stream/consumer diff. + for _, cas := range ru.removeConsumers { + for _, ca := range cas { + if asa, ok := streams[ca.Client.serviceAccount()]; ok { + if sa, ok := asa[ca.Stream]; ok { + delete(sa.consumers, ca.Name) + } + } + } + } + for _, sa := range ru.removeStreams { + if asa, ok := streams[sa.Client.serviceAccount()]; ok { + delete(asa, sa.Config.Name) + } + } + for _, sa := range ru.addStreams { + as := streams[sa.Client.serviceAccount()] + if as == nil { + as = make(map[string]*streamAssignment) + streams[sa.Client.serviceAccount()] = as + } + // Preserve consumers from the previous assignment. + if osa := as[sa.Config.Name]; osa != nil { + sa.consumers = osa.consumers + } + as[sa.Config.Name] = sa + } + for _, cas := range ru.updateConsumers { + for _, ca := range cas { + if asa, ok := streams[ca.Client.serviceAccount()]; ok { + if sa, ok := asa[ca.Stream]; ok { + if sa.consumers == nil { + sa.consumers = make(map[string]*consumerAssignment) + } + sa.consumers[ca.Name] = ca + } + } + } + } return nil } @@ -1937,31 +2308,31 @@ func (js *jetStream) processAddPeer(peer string) { } si := sir.(nodeInfo) - for _, asa := range cc.streams { - for _, sa := range asa { - if sa.unsupported != nil { + for accName, sa := range js.streamAssignmentsOrInflightSeqAllAccounts() { + if sa.unsupported != nil { + continue + } + if sa.missingPeers() { + // Make sure the right cluster etc. + if si.cluster != sa.Client.Cluster { continue } - if sa.missingPeers() { - // Make sure the right cluster etc. - if si.cluster != sa.Client.Cluster { + // If we are here we can add in this peer. + csa := sa.copyGroup() + csa.Group.Peers = append(csa.Group.Peers, peer) + // Send our proposal for this csa. Also use same group definition for all the consumers as well. + cc.meta.Propose(encodeAddStreamAssignment(csa)) + cc.trackInflightStreamProposal(accName, csa, false) + for ca := range js.consumerAssignmentsOrInflightSeq(accName, csa.Config.Name) { + if ca.unsupported != nil { continue } - // If we are here we can add in this peer. - csa := sa.copyGroup() - csa.Group.Peers = append(csa.Group.Peers, peer) - // Send our proposal for this csa. Also use same group definition for all the consumers as well. - cc.meta.Propose(encodeAddStreamAssignment(csa)) - for _, ca := range sa.consumers { - if ca.unsupported != nil { - continue - } - // Ephemerals are R=1, so only auto-remap durables, or R>1. - if ca.Config.Durable != _EMPTY_ || len(ca.Group.Peers) > 1 { - cca := ca.copyGroup() - cca.Group.Peers = csa.Group.Peers - cc.meta.Propose(encodeAddConsumerAssignment(cca)) - } + // Ephemerals are R=1, so only auto-remap durables, or R>1. + if ca.Config.Durable != _EMPTY_ || len(ca.Group.Peers) > 1 { + cca := ca.copyGroup() + cca.Group.Peers = csa.Group.Peers + cc.meta.Propose(encodeAddConsumerAssignment(cca)) + cc.trackInflightConsumerProposal(accName, csa.Config.Name, cca, false) } } } @@ -2011,14 +2382,12 @@ func (js *jetStream) processRemovePeer(peer string) { js.mu.Lock() defer js.mu.Unlock() - for _, asa := range cc.streams { - for _, sa := range asa { - if sa.unsupported != nil { - continue - } - if rg := sa.Group; rg.isMember(peer) { - js.removePeerFromStreamLocked(sa, peer) - } + for _, sa := range js.streamAssignmentsOrInflightSeqAllAccounts() { + if sa.unsupported != nil { + continue + } + if rg := sa.Group; rg.isMember(peer) { + js.removePeerFromStreamLocked(sa, peer) } } } @@ -2041,14 +2410,16 @@ func (js *jetStream) removePeerFromStreamLocked(sa *streamAssignment, peer strin return false } replaced := cc.remapStreamAssignment(csa, peer) + accName := sa.Client.serviceAccount() if !replaced { - s.Warnf("JetStream cluster could not replace peer for stream '%s > %s'", sa.Client.serviceAccount(), sa.Config.Name) + s.Warnf("JetStream cluster could not replace peer for stream '%s > %s'", accName, sa.Config.Name) } // Send our proposal for this csa. Also use same group definition for all the consumers as well. cc.meta.Propose(encodeAddStreamAssignment(csa)) + cc.trackInflightStreamProposal(accName, csa, false) rg := csa.Group - for _, ca := range sa.consumers { + for ca := range js.consumerAssignmentsOrInflightSeq(accName, sa.Config.Name) { if ca.unsupported != nil { continue } @@ -2057,9 +2428,11 @@ func (js *jetStream) removePeerFromStreamLocked(sa *streamAssignment, peer strin cca := ca.copyGroup() cca.Group.Peers, cca.Group.Preferred = rg.Peers, _EMPTY_ cc.meta.Propose(encodeAddConsumerAssignment(cca)) + cc.trackInflightConsumerProposal(accName, csa.Config.Name, cca, false) } else if ca.Group.isMember(peer) { // These are ephemerals. Check to see if we deleted this peer. cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) + cc.trackInflightConsumerProposal(accName, csa.Config.Name, ca, true) } } return replaced @@ -2101,7 +2474,6 @@ func (ca *consumerAssignment) recoveryKey() string { func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bool, bool, error) { var didSnap bool isRecovering := ru != nil - startupRecovery := js.isMetaRecovering() for _, e := range entries { // If we received a lower-level catchup entry, mark that we're recovering. @@ -2115,7 +2487,9 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo } if e.Type == EntrySnapshot { - js.applyMetaSnapshot(e.Data, ru, isRecovering, startupRecovery) + if err := js.applyMetaSnapshot(e.Data, ru, isRecovering); err != nil { + return isRecovering, didSnap, err + } didSnap = true } else if e.Type == EntryRemovePeer { if !js.isMetaRecovering() { @@ -2570,9 +2944,10 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps }() const ( - compactInterval = 2 * time.Minute - compactSizeMin = 8 * 1024 * 1024 - compactNumMin = 65536 + compactInterval = 2 * time.Minute + compactMinInterval = 15 * time.Second + compactSizeMin = 8 * 1024 * 1024 + compactNumMin = 65536 ) // Spread these out for large numbers on server restart. @@ -2603,8 +2978,11 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps // fully recovered from disk. isRecovering := true - doSnapshot := func() { - if mset == nil || isRecovering || isRestore { + var failedSnapshots int + doSnapshot := func(force bool) { + // Suppress during recovery. + // If snapshots have failed, and we're not forced to, we'll wait for the timer since it'll now be forced. + if mset == nil || isRecovering || isRestore || (!force && failedSnapshots > 0) { return } @@ -2623,10 +3001,27 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps // Make sure all pending data is flushed before allowing snapshots. mset.flushAllPending() - if err := n.InstallSnapshot(mset.stateSnapshot()); err == nil { + + // If we had a significant number of failed snapshots, start relaxing Raft-layer checks + // to force it through. We might have been catching up a peer for a long period, and this + // protects our log size from growing indefinitely. + forceSnapshot := failedSnapshots > 4 + if err := n.InstallSnapshot(mset.stateSnapshot(), forceSnapshot); err == nil { lastState = curState + // If there was a failed snapshot before, we reduced the timer's interval. + // Reset it back to the original interval now. + if failedSnapshots > 0 { + t.Reset(compactInterval + rci) + } + failedSnapshots = 0 } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { s.RateLimitWarnf("Failed to install snapshot for '%s > %s' [%s]: %v", mset.acc.Name, mset.name(), n.Group(), err) + // If this is the first failure, reduce the interval of the snapshot timer. + // This ensures we're not waiting too long for snapshotting to eventually become forced. + if failedSnapshots == 0 { + t.Reset(compactMinInterval) + } + failedSnapshots++ } } @@ -2703,14 +3098,14 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps select { case <-s.quitCh: // Server shutting down, but we might receive this before qch, so try to snapshot. - doSnapshot() + doSnapshot(false) return case <-mqch: // Clean signal from shutdown routine so do best effort attempt to snapshot. // Don't snapshot if not shutting down, monitor goroutine could be going away // on a scale down or a remove for example. if s.isShuttingDown() { - doSnapshot() + doSnapshot(false) } return case <-qch: @@ -2808,7 +3203,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps // Check about snapshotting // If we have at least min entries to compact, go ahead and try to snapshot/compact. if ne >= compactNumMin || nb > compactSizeMin || mset.getCLFS() > pclfs { - doSnapshot() + doSnapshot(false) } case isLeader = <-lch: @@ -2827,7 +3222,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps restoreDoneCh = s.processStreamRestore(sa.Client, acc, sa.Config, _EMPTY_, sa.Reply, _EMPTY_) continue } else if n != nil && n.NeedSnapshot() { - doSnapshot() + doSnapshot(false) } // Always cancel if this was running. stopDirectMonitoring() @@ -2903,7 +3298,9 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps stopDirectMonitoring() case <-t.C: - doSnapshot() + // Start forcing snapshots if they failed previously. + forceIfFailed := failedSnapshots > 0 + doSnapshot(forceIfFailed) case <-uch: // keep stream assignment current @@ -3233,7 +3630,7 @@ func (mset *stream) resetClusteredState(err error) bool { js.processClusterCreateStream(acc, sa) // Reset consumers. for _, ca := range consumers { - js.processClusterCreateConsumer(ca, nil, false) + js.processClusterCreateConsumer(nil, ca, nil, false) } } }() @@ -4048,7 +4445,7 @@ func (s *Server) sendStreamLeaderElectAdvisory(mset *stream) { s.publishAdvisory(nil, subj, adv) } -// Will lookup a stream assignment. +// Will look up a stream assignment. // Lock should be held. func (js *jetStream) streamAssignment(account, stream string) (sa *streamAssignment) { cc := js.cluster @@ -4062,6 +4459,86 @@ func (js *jetStream) streamAssignment(account, stream string) (sa *streamAssignm return sa } +// Will look up a stream assignment, either an applied or inflight assignment. +// Lock should be held. +func (js *jetStream) streamAssignmentOrInflight(account, stream string) *streamAssignment { + cc := js.cluster + if cc == nil { + return nil + } + if streams, ok := cc.inflightStreams[account]; ok { + if inflight, ok := streams[stream]; ok { + if !inflight.deleted { + return inflight.streamAssignment + } else { + return nil + } + } + } + + if as := cc.streams[account]; as != nil { + return as[stream] + } + return nil +} + +// Will gather all stream assignments for a specific account, both applied and inflight assignments. +// Lock should be held. +func (js *jetStream) streamAssignmentsOrInflightSeq(account string) iter.Seq[*streamAssignment] { + return func(yield func(*streamAssignment) bool) { + cc := js.cluster + if cc == nil { + return + } + inflight := cc.inflightStreams[account] + for _, i := range inflight { + if !i.deleted && !yield(i.streamAssignment) { + return + } + } + for _, sa := range cc.streams[account] { + // Skip if we already iterated over it as inflight. + if _, ok := inflight[sa.Config.Name]; ok { + continue + } + if !yield(sa) { + return + } + } + } +} + +// Will gather all stream assignments for all accounts, both applied and inflight assignments. +// Lock should be held. +func (js *jetStream) streamAssignmentsOrInflightSeqAllAccounts() iter.Seq2[string, *streamAssignment] { + return func(yield func(string, *streamAssignment) bool) { + cc := js.cluster + if cc == nil { + return + } + for accName, inflight := range cc.inflightStreams { + for _, i := range inflight { + if !i.deleted && !yield(accName, i.streamAssignment) { + return + } + } + } + for accName, asa := range cc.streams { + for _, sa := range asa { + // Skip if we already iterated over it as inflight. + if inflight, ok := cc.inflightStreams[accName]; ok { + if _, ok := inflight[sa.Config.Name]; ok { + continue + } + } + if !yield(accName, sa) { + return + } + } + } + } +} + // processStreamAssignment is called when followers have replicated an assignment. func (js *jetStream) processStreamAssignment(sa *streamAssignment) { js.mu.Lock() @@ -4077,14 +4554,14 @@ func (js *jetStream) processStreamAssignment(sa *streamAssignment) { isMember = sa.Group.isMember(ourID) } - // Remove this stream from the inflight proposals - cc.removeInflightProposal(accName, sa.Config.Name) - if s == nil || noMeta { js.mu.Unlock() return } + // Remove this stream from the inflight proposals + cc.removeInflightStreamProposal(accName, sa.Config.Name) + accStreams := cc.streams[accName] if accStreams == nil { accStreams = make(map[string]*streamAssignment) @@ -4189,6 +4666,10 @@ func (js *jetStream) processUpdateStreamAssignment(sa *streamAssignment) { js.mu.Unlock() return } + + // Remove this stream from the inflight proposals + cc.removeInflightStreamProposal(accName, sa.Config.Name) + ourID := cc.meta.ID() var isMember bool @@ -4339,6 +4820,8 @@ func (js *jetStream) processClusterUpdateStream(acc *Account, osa, sa *streamAss s.Warnf("JetStream cluster detected stream remapping for '%s > %s' from %q to %q", acc, cfg.Name, osa.Group.Name, sa.Group.Name) mset.removeNode() + mset.signalMonitorQuit() + mset.monitorWg.Wait() alreadyRunning, needsNode = false, true // Make sure to clear from original. js.mu.Lock() @@ -4376,6 +4859,8 @@ func (js *jetStream) processClusterUpdateStream(acc *Account, osa, sa *streamAss } else if numReplicas == 1 && alreadyRunning { // We downgraded to R1. Make sure we cleanup the raft node and the stream monitor. mset.removeNode() + mset.signalMonitorQuit() + mset.monitorWg.Wait() // In case we need to shutdown the cluster specific subs, etc. mset.mu.Lock() // Stop responding to sync requests. @@ -4560,7 +5045,7 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme } } else if err == NewJSStreamNotFoundError() { // Add in the stream here. - mset, err = acc.addStreamWithAssignment(sa.Config, nil, sa, false) + mset, err = acc.addStreamWithAssignment(sa.Config, nil, sa, false, false) } if mset != nil { mset.setCreatedTime(created) @@ -4761,6 +5246,7 @@ func (js *jetStream) processStreamRemoval(sa *streamAssignment) { delete(cc.streams, accName) } } + cc.removeInflightStreamProposal(accName, sa.Config.Name) js.mu.Unlock() // During initial/startup recovery we'll not have registered the stream assignment, @@ -4904,16 +5390,15 @@ func (js *jetStream) processConsumerAssignment(ca *consumerAssignment) { return } - // Might need this below. - numReplicas := sa.Config.Replicas - // Track if this existed already. var wasExisting bool // Check if we have an existing consumer assignment. if sa.consumers == nil { sa.consumers = make(map[string]*consumerAssignment) - } else if oca := sa.consumers[ca.Name]; oca != nil { + } + oca := sa.consumers[ca.Name] + if oca != nil { wasExisting = true // Copy over private existing state from former CA. if ca.Group != nil { @@ -4940,7 +5425,7 @@ func (js *jetStream) processConsumerAssignment(ca *consumerAssignment) { // Place into our internal map under the stream assignment. // Ok to replace an existing one, we check on process call below. sa.consumers[ca.Name] = ca - ca.pending = false + cc.removeInflightConsumerProposal(accName, stream, consumerName) // If unsupported, we can't register any further. if ca.unsupported != nil { @@ -4995,65 +5480,45 @@ func (js *jetStream) processConsumerAssignment(ca *consumerAssignment) { // Check if this is for us.. if isMember { - js.processClusterCreateConsumer(ca, state, wasExisting) - } else { - // We need to be removed here, we are no longer assigned. - // Grab consumer if we have it. - var o *consumer - if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { - o = mset.lookupConsumer(ca.Name) - } - - // Check if we have a raft node running, meaning we are no longer part of the group but were. - js.mu.Lock() - if node := ca.Group.node; node != nil { + js.processClusterCreateConsumer(oca, ca, state, wasExisting) + } else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { + if o := mset.lookupConsumer(ca.Name); o != nil { // We have one here even though we are not a member. This can happen on re-assignment. - s.Debugf("JetStream removing consumer '%s > %s > %s' from this server", sa.Client.serviceAccount(), sa.Config.Name, ca.Name) - if node.Leader() { - s.Debugf("JetStream consumer '%s > %s > %s' is being removed and was the leader, will perform stepdown", - sa.Client.serviceAccount(), sa.Config.Name, ca.Name) + s.removeConsumer(o, ca) + } + } +} - peers, cn := node.Peers(), s.cachedClusterName() - migrating := numReplicas != len(peers) +// Common function to remove ourselves from this server. +// This can happen on re-assignment, move, etc +func (s *Server) removeConsumer(o *consumer, nca *consumerAssignment) { + if o == nil { + return + } + // Make sure to use the new stream assignment, not our own. + s.Debugf("JetStream removing consumer '%s > %s > %s' from this server", nca.Client.serviceAccount(), nca.Stream, nca.Name) + if node := o.raftNode(); node != nil { + node.StepDown(nca.Group.Preferred) + // shutdown monitor by shutting down raft. + node.Delete() + } - // Select a new peer to transfer to. If we are a migrating make sure its from the new cluster. - var npeer string - for _, r := range peers { - if !r.Current { - continue - } - if !migrating { - npeer = r.ID - break - } else if sir, ok := s.nodeToInfo.Load(r.ID); ok && sir != nil { - si := sir.(nodeInfo) - if si.cluster != cn { - npeer = r.ID - break - } - } - } - // Clear the raftnode from our consumer so that a subsequent o.delete will not also issue a stepdown. - if o != nil { - o.clearRaftNode() - } - // Manually handle the stepdown and deletion of the node. - node.UpdateKnownPeers(ca.Group.Peers) - node.StepDown(npeer) - node.Delete() - } else { - node.UpdateKnownPeers(ca.Group.Peers) - } - } - // Always clear the old node. - ca.Group.node = nil - ca.err = nil + var isShuttingDown bool + // Make sure this node is no longer attached to our consumer assignment. + if js, _ := s.getJetStreamCluster(); js != nil { + js.mu.Lock() + nca.Group.node = nil + nca.err = nil + isShuttingDown = js.shuttingDown js.mu.Unlock() + } - if o != nil { - o.deleteWithoutAdvisory() - } + if !isShuttingDown { + // wait for monitor to be shutdown. + o.signalMonitorQuit() + o.monitorWg.Wait() } + o.deleteWithoutAdvisory() } func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { @@ -5076,7 +5541,6 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { // Make sure this removal is for what we have, otherwise ignore. if ca.Group != nil && oca.Group != nil && ca.Group.Name == oca.Group.Name { needDelete = true - oca.deleted = true delete(sa.consumers, ca.Name) // Remember we used to be unsupported, just so we can send a successful delete response. if ca.unsupported == nil { @@ -5085,6 +5549,7 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { } } } + cc.removeInflightConsumerProposal(accName, stream, name) js.mu.Unlock() // During initial/startup recovery we'll not have registered the consumer assignment, @@ -5113,7 +5578,7 @@ type consumerAssignmentResult struct { } // processClusterCreateConsumer is when we are a member of the group and need to create the consumer. -func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state *ConsumerState, wasExisting bool) { +func (js *jetStream) processClusterCreateConsumer(oca, ca *consumerAssignment, state *ConsumerState, wasExisting bool) { if ca == nil { return } @@ -5154,6 +5619,19 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state // Check if we already have this consumer running. o := mset.lookupConsumer(consumer) + if o != nil && oca != nil && oca.Group.Name != ca.Group.Name { + s.Warnf("JetStream cluster detected consumer remapping for '%s > %s' from %q to %q", + acc, ca.Name, oca.Group.Name, ca.Group.Name) + o.clearNode() + o.signalMonitorQuit() + o.monitorWg.Wait() + alreadyRunning = false + // Make sure to clear from original. + js.mu.Lock() + oca.Group.node = nil + js.mu.Unlock() + } + // Process the raft group and make sure it's running if needed. storage := mset.config().Storage if ca.Config.MemoryStorage { @@ -5312,20 +5790,43 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state // Check for scale down to 1.. if node != nil && len(rg.Peers) == 1 { o.clearNode() - o.setLeader(true) + o.signalMonitorQuit() + o.monitorWg.Wait() // Need to clear from rg too. js.mu.Lock() rg.node = nil client, subject, reply := ca.Client, ca.Subject, ca.Reply js.mu.Unlock() - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.info()) - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) + // Perform the leader change in a goroutine, otherwise we could block meta operations. + if o.shouldStartMonitor() { + started := s.startGoRoutine( + func() { + defer s.grWG.Done() + defer o.clearMonitorRunning() + o.setLeader(true) + var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} + resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.info()) + s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) + }, + pprofLabels{ + "type": "consumer", + "account": mset.accName(), + "stream": mset.name(), + "consumer": ca.Name, + }, + ) + if !started { + o.clearMonitorRunning() + } + } return } } if node == nil { + // Wait for the previous routine to stop running. + o.signalMonitorQuit() + o.monitorWg.Wait() // Single replica consumer, process manually here. js.mu.Lock() // Force response in case we think this is an update. @@ -5333,13 +5834,15 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state ca.responded = false } js.mu.Unlock() - js.processConsumerLeaderChange(o, true) - } else { - // Clustered consumer. - // Start our monitoring routine if needed. - if !alreadyRunning && o.shouldStartMonitor() { + cca := o.consumerAssignment() + // Perform the leader change in a goroutine, otherwise we could block meta operations. + if o.shouldStartMonitor() { started := s.startGoRoutine( - func() { js.monitorConsumer(o, ca) }, + func() { + defer s.grWG.Done() + defer o.clearMonitorRunning() + js.processConsumerLeaderChangeWithAssignment(o, cca, true) + }, pprofLabels{ "type": "consumer", "account": mset.accName(), @@ -5351,6 +5854,28 @@ func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state o.clearMonitorRunning() } } + } else { + // Clustered consumer. + // Start our monitoring routine if needed. + if !alreadyRunning { + // Wait for the previous routine to stop running. + o.signalMonitorQuit() + o.monitorWg.Wait() + if o.shouldStartMonitor() { + started := s.startGoRoutine( + func() { js.monitorConsumer(o, ca) }, + pprofLabels{ + "type": "consumer", + "account": mset.accName(), + "stream": mset.name(), + "consumer": ca.Name, + }, + ) + if !started { + o.clearMonitorRunning() + } + } + } // For existing consumer, only send response if not recovering. if wasExisting && !js.isMetaRecovering() { if o.IsLeader() || (!didCreate && needsLocalResponse) { @@ -5446,6 +5971,64 @@ func (js *jetStream) consumerAssignment(account, stream, consumer string) *consu return nil } +// Will look up a consumer assignment, either an applied or inflight assignment. +// Lock should be held. +func (js *jetStream) consumerAssignmentOrInflight(account, stream, consumer string) *consumerAssignment { + cc := js.cluster + if cc == nil { + return nil + } + if streams, ok := cc.inflightConsumers[account]; ok { + if consumers, ok := streams[stream]; ok { + if inflight, ok := consumers[consumer]; ok { + if !inflight.deleted { + return inflight.consumerAssignment + } else { + return nil + } + } + } + } + if sa := js.streamAssignment(account, stream); sa != nil { + return sa.consumers[consumer] + } + return nil +} + +// Will gather all consumer assignments for the specified account and stream, both applied and inflight assignments. +// Lock should be held. +func (js *jetStream) consumerAssignmentsOrInflightSeq(account, stream string) iter.Seq[*consumerAssignment] { + return func(yield func(*consumerAssignment) bool) { + cc := js.cluster + if cc == nil { + return + } + + var inflight map[string]*inflightConsumerInfo + if streams, ok := cc.inflightConsumers[account]; ok { + inflight = streams[stream] + } + for _, i := range inflight { + if !i.deleted && !yield(i.consumerAssignment) { + return + } + } + sa := js.streamAssignment(account, stream) + if sa == nil { + return + } + for _, ca := range sa.consumers { + // Skip if we already iterated over it as inflight. + if _, ok := inflight[ca.Name]; ok { + continue + } + if !yield(ca) { + return + } + } + } +} + // consumerAssigned informs us if this server has this consumer assigned. func (jsa *jsAccount) consumerAssigned(stream, consumer string) bool { jsa.mu.RLock() @@ -5521,15 +6104,6 @@ func (o *consumer) raftGroup() *raftGroup { return o.ca.Group } -func (o *consumer) clearRaftNode() { - if o == nil { - return - } - o.mu.Lock() - defer o.mu.Unlock() - o.node = nil -} - func (o *consumer) raftNode() RaftNode { if o == nil { return nil @@ -5561,10 +6135,11 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { defer s.Debugf("Exiting consumer monitor for '%s > %s > %s' [%s]", o.acc.Name, ca.Stream, ca.Name, n.Group()) const ( - compactInterval = 2 * time.Minute - compactSizeMin = 64 * 1024 // What is stored here is always small for consumers. - compactNumMin = 1024 - minSnapDelta = 10 * time.Second + compactInterval = 2 * time.Minute + compactMinInterval = 15 * time.Second + compactSizeMin = 64 * 1024 // What is stored here is always small for consumers. + compactNumMin = 1024 + minSnapDelta = 10 * time.Second ) // Spread these out for large numbers on server restart. @@ -5584,9 +6159,11 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { // fully recovered from disk. recovering := true + var failedSnapshots int doSnapshot := func(force bool) { // Bail if trying too fast and not in a forced situation. - if recovering || (!force && time.Since(lastSnapTime) < minSnapDelta) { + // If snapshots have failed, and we're not forced to, we'll wait for the timer since it'll now be forced. + if recovering || (!force && (time.Since(lastSnapTime) < minSnapDelta || failedSnapshots > 0)) { return } @@ -5607,10 +6184,26 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { // lot on an idle stream, log entries get distributed but the // state never changes, therefore the log never gets compacted. if !bytes.Equal(hash[:], lastSnap) || ne >= compactNumMin || nb >= compactSizeMin { - if err := n.InstallSnapshot(snap); err == nil { + // If we had a significant number of failed snapshots, start relaxing Raft-layer checks + // to force it through. We might have been catching up a peer for a long period, and this + // protects our log size from growing indefinitely. + forceSnapshot := failedSnapshots > 4 + if err := n.InstallSnapshot(snap, forceSnapshot); err == nil { lastSnap, lastSnapTime = hash[:], time.Now() + // If there was a failed snapshot before, we reduced the timer's interval. + // Reset it back to the original interval now. + if failedSnapshots > 0 { + t.Reset(compactInterval + rci) + } + failedSnapshots = 0 } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { s.RateLimitWarnf("Failed to install snapshot for '%s > %s > %s' [%s]: %v", o.acc.Name, ca.Stream, ca.Name, n.Group(), err) + // If this is the first failure, reduce the interval of the snapshot timer. + // This ensures we're not waiting too long for snapshotting to eventually become forced. + if failedSnapshots == 0 { + t.Reset(compactMinInterval) + } + failedSnapshots++ } } } @@ -5783,7 +6376,9 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { } case <-t.C: - doSnapshot(false) + // Start forcing snapshots if they failed previously. + forceIfFailed := failedSnapshots > 0 + doSnapshot(forceIfFailed) } } } @@ -5809,7 +6404,7 @@ func (js *jetStream) applyConsumerEntries(o *consumer, ce *CommittedEntry, isLea panic(err.Error()) } - if err = o.store.Update(state); err != nil { + if err = o.store.Update(state); err != nil && err != ErrStoreOldUpdate { o.mu.RLock() s, acc, mset, name := o.srv, o.acc, o.mset, o.name o.mu.RUnlock() @@ -6027,6 +6622,10 @@ func decodeDeliveredUpdate(buf []byte) (dseq, sseq, dc uint64, ts int64, err err } func (js *jetStream) processConsumerLeaderChange(o *consumer, isLeader bool) error { + return js.processConsumerLeaderChangeWithAssignment(o, nil, isLeader) +} + +func (js *jetStream) processConsumerLeaderChangeWithAssignment(o *consumer, ca *consumerAssignment, isLeader bool) error { stepDownIfLeader := func() error { if node := o.raftNode(); node != nil && isLeader { node.StepDown() @@ -6038,7 +6637,9 @@ func (js *jetStream) processConsumerLeaderChange(o *consumer, isLeader bool) err return stepDownIfLeader() } - ca := o.consumerAssignment() + if ca == nil { + ca = o.consumerAssignment() + } if ca == nil { return stepDownIfLeader() } @@ -6218,12 +6819,7 @@ func (js *jetStream) processStreamAssignmentResults(sub *subscription, c *client return } - // This should have been done already in processStreamAssignment, but in - // case we have a code path that gets here with no processStreamAssignment, - // then we will do the proper thing. Otherwise will be a no-op. - cc.removeInflightProposal(result.Account, result.Stream) - - if sa := js.streamAssignment(result.Account, result.Stream); sa != nil && !sa.reassigning { + if sa := js.streamAssignmentOrInflight(result.Account, result.Stream); sa != nil && !sa.reassigning { canDelete := !result.Update && time.Since(sa.Created) < 5*time.Second // See if we should retry in case this cluster is full but there are others. @@ -6246,9 +6842,11 @@ func (js *jetStream) processStreamAssignmentResults(sub *subscription, c *client rg.setPreferred(s) // Get rid of previous attempt. cc.meta.Propose(encodeDeleteStreamAssignment(sa)) + cc.trackInflightStreamProposal(result.Account, sa, true) // Propose new. sa.Group, sa.err = rg, nil cc.meta.Propose(encodeAddStreamAssignment(sa)) + cc.trackInflightStreamProposal(result.Account, sa, false) // When the new stream assignment is processed, sa.reassigning will be // automatically set back to false. Until then, don't process any more // assignment results. @@ -6274,6 +6872,7 @@ func (js *jetStream) processStreamAssignmentResults(sub *subscription, c *client if canDelete { sa.err = NewJSClusterNotAssignedError() cc.meta.Propose(encodeDeleteStreamAssignment(sa)) + cc.trackInflightStreamProposal(result.Account, sa, true) } } } @@ -6443,6 +7042,10 @@ func (js *jetStream) processLeaderChange(isLeader bool) { // Clear replies for peer-removes. js.cluster.peerRemoveReply = nil + // Clear inflight proposal tracking. + js.cluster.inflightStreams = nil + js.cluster.inflightConsumers = nil + if isLeader { if meta := js.cluster.meta; meta != nil && meta.IsObserver() { meta.StepDown() @@ -6461,17 +7064,16 @@ func (js *jetStream) processLeaderChange(isLeader bool) { // assignments with no sync subject after an update and no way to sync/catchup outside of the RAFT layer. if isLeader && js.cluster.streamsCheck { cc := js.cluster - for acc, asa := range cc.streams { - for _, sa := range asa { - if sa.unsupported != nil { - continue - } - if sa.Sync == _EMPTY_ { - s.Warnf("Stream assignment corrupt for stream '%s > %s'", acc, sa.Config.Name) - nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client, Created: sa.Created} - nsa.Sync = syncSubjForStream() - cc.meta.Propose(encodeUpdateStreamAssignment(nsa)) - } + for acc, sa := range js.streamAssignmentsOrInflightSeqAllAccounts() { + if sa.unsupported != nil { + continue + } + if sa.Sync == _EMPTY_ { + s.Warnf("Stream assignment corrupt for stream '%s > %s'", acc, sa.Config.Name) + nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client, Created: sa.Created} + nsa.Sync = syncSubjForStream() + cc.meta.Propose(encodeUpdateStreamAssignment(nsa)) + cc.trackInflightStreamProposal(acc, nsa, false) } } // Clear check. @@ -6927,20 +7529,23 @@ func groupName(prefix string, peers []string, storage StorageType) string { // returns stream count for this tier as well as applicable reservation size (not including cfg) // jetStream read lock should be held -func tieredStreamAndReservationCount(asa map[string]*streamAssignment, tier string, cfg *StreamConfig) (int, int64) { +func (js *jetStream) tieredStreamAndReservationCount(accName, tier string, cfg *StreamConfig) (int, int64) { var numStreams int var reservation int64 - for _, sa := range asa { + for sa := range js.streamAssignmentsOrInflightSeq(accName) { // Don't count the stream toward the limit if it already exists. - if (tier == _EMPTY_ || isSameTier(sa.Config, cfg)) && sa.Config.Name != cfg.Name { + if sa.Config.Name == cfg.Name { + continue + } + if tier == _EMPTY_ || isSameTier(sa.Config, cfg) { numStreams++ if sa.Config.MaxBytes > 0 && sa.Config.Storage == cfg.Storage { // If tier is empty, all storage is flat and we should adjust for replicas. // Otherwise if tiered, storage replication already taken into consideration. - if tier == _EMPTY_ && cfg.Replicas > 1 { - reservation += sa.Config.MaxBytes * int64(cfg.Replicas) + if tier == _EMPTY_ && sa.Config.Replicas > 1 { + reservation = addSaturate(reservation, mulSaturate(int64(sa.Config.Replicas), sa.Config.MaxBytes)) } else { - reservation += sa.Config.MaxBytes + reservation = addSaturate(reservation, sa.Config.MaxBytes) } } } @@ -7012,19 +7617,7 @@ func (js *jetStream) jsClusteredStreamLimitsCheck(acc *Account, cfg *StreamConfi return apiErr } - asa := js.cluster.streams[acc.Name] - numStreams, reservations := tieredStreamAndReservationCount(asa, tier, cfg) - // Check for inflight proposals... - if cc := js.cluster; cc != nil && cc.inflight != nil { - streams := cc.inflight[acc.Name] - numStreams += len(streams) - // If inflight contains the same stream, don't count toward exceeding maximum. - if cfg != nil { - if _, ok := streams[cfg.Name]; ok { - numStreams-- - } - } - } + numStreams, reservations := js.tieredStreamAndReservationCount(acc.Name, tier, cfg) if selectedLimits.MaxStreams > 0 && numStreams >= selectedLimits.MaxStreams { return NewJSMaximumStreamsLimitError() } @@ -7059,9 +7652,20 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, var rg *raftGroup var syncSubject string - // Capture if we have existing assignment first. - if osa := js.streamAssignment(acc.Name, cfg.Name); osa != nil { + // Capture if we have existing/inflight assignment first. + if osa := js.streamAssignmentOrInflight(acc.Name, cfg.Name); osa != nil { copyStreamMetadata(cfg, osa.Config) + // Set the index name on both to ensure the DeepEqual works + currentIName := make(map[string]struct{}) + for _, s := range osa.Config.Sources { + currentIName[s.iname] = struct{}{} + } + for _, s := range cfg.Sources { + s.setIndexName() + if _, ok := currentIName[s.iname]; !ok { + s.iname = _EMPTY_ + } + } if !reflect.DeepEqual(osa.Config, cfg) { resp.Error = NewJSStreamNameExistError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) @@ -7078,7 +7682,7 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, } // Check for subject collisions here. - if cc.subjectsOverlap(acc.Name, cfg.Subjects, self) { + if js.subjectsOverlap(acc.Name, cfg.Subjects, self) { resp.Error = NewJSStreamSubjectOverlapError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return @@ -7092,29 +7696,6 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, return } - // Make sure inflight is setup properly. - if cc.inflight == nil { - cc.inflight = make(map[string]map[string]*inflightInfo) - } - streams, ok := cc.inflight[acc.Name] - if !ok { - streams = make(map[string]*inflightInfo) - cc.inflight[acc.Name] = streams - } - - // Raft group selection and placement. - if rg == nil { - // Check inflight before proposing in case we have an existing inflight proposal. - if existing, ok := streams[cfg.Name]; ok { - if !reflect.DeepEqual(existing.cfg, cfg) { - resp.Error = NewJSStreamNameExistError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // We have existing for same stream. Re-use same group and syncSubject. - rg, syncSubject = existing.rg, existing.sync - } - } // Create a new one here if needed. if rg == nil { nrg, err := js.createGroupForStream(ci, cfg) @@ -7137,9 +7718,7 @@ func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, // On success, add this as an inflight proposal so we can apply limits // on concurrent create requests while this stream assignment has // possibly not been processed yet. - if streams, ok := cc.inflight[acc.Name]; ok && self == nil { - streams[cfg.Name] = &inflightInfo{rg, syncSubject, cfg} - } + cc.trackInflightStreamProposal(acc.Name, sa, false) } } @@ -7213,7 +7792,7 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - osa := js.streamAssignment(acc.Name, cfg.Name) + osa := js.streamAssignmentOrInflight(acc.Name, cfg.Name) if osa == nil { resp.Error = NewJSStreamNotFoundError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) @@ -7256,7 +7835,7 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su } // Check for subject collisions here. - if cc.subjectsOverlap(acc.Name, cfg.Subjects, osa) { + if js.subjectsOverlap(acc.Name, cfg.Subjects, osa) { resp.Error = NewJSStreamSubjectOverlapError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return @@ -7412,7 +7991,7 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su } // Need to remap any consumers. - for _, ca := range osa.consumers { + for ca := range js.consumerAssignmentsOrInflightSeq(acc.Name, osa.Config.Name) { // Legacy ephemerals are R=1 but present as R=0, so only auto-remap named consumers, or if we are downsizing the consumer peers. // If stream is interest or workqueue policy always remaps since they require peer parity with stream. numPeers := len(ca.Group.Peers) @@ -7510,7 +8089,7 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su } rg.Peers = peerSet - for _, ca := range osa.consumers { + for ca := range js.consumerAssignmentsOrInflightSeq(acc.Name, osa.Config.Name) { cca := ca.copyGroup() r := cca.Config.replicas(osa.Config) // shuffle part of cluster peer set we will be keeping @@ -7554,11 +8133,15 @@ func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, su } sa := &streamAssignment{Group: rg, Sync: osa.Sync, Created: osa.Created, Config: newCfg, Subject: subject, Reply: reply, Client: ci} - meta.Propose(encodeUpdateStreamAssignment(sa)) + if err := meta.Propose(encodeUpdateStreamAssignment(sa)); err != nil { + return + } + cc.trackInflightStreamProposal(acc.Name, sa, false) // Process any staged consumers. for _, ca := range consumers { meta.Propose(encodeAddConsumerAssignment(ca)) + cc.trackInflightConsumerProposal(acc.Name, sa.Config.Name, ca, false) } } @@ -7575,7 +8158,7 @@ func (s *Server) jsClusteredStreamDeleteRequest(ci *ClientInfo, acc *Account, st return } - osa := js.streamAssignment(acc.Name, stream) + osa := js.streamAssignmentOrInflight(acc.Name, stream) if osa == nil { var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}} resp.Error = NewJSStreamNotFoundError() @@ -7584,7 +8167,9 @@ func (s *Server) jsClusteredStreamDeleteRequest(ci *ClientInfo, acc *Account, st } sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Reply: reply, Client: ci, Created: osa.Created} - cc.meta.Propose(encodeDeleteStreamAssignment(sa)) + if err := cc.meta.Propose(encodeDeleteStreamAssignment(sa)); err == nil { + cc.trackInflightStreamProposal(acc.Name, sa, true) + } } // Process a clustered purge request. @@ -7645,6 +8230,16 @@ func (s *Server) jsClusteredStreamRestoreRequest( return } + resp := JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} + + // check stream config at the start of the restore process, not at the end + cfg, apiErr := s.checkStreamCfg(&req.Config, acc, false) + if apiErr != nil { + resp.Error = apiErr + s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) + return + } + js.mu.Lock() defer js.mu.Unlock() @@ -7652,23 +8247,20 @@ func (s *Server) jsClusteredStreamRestoreRequest( return } - cfg := &req.Config - resp := JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} - - if err := js.jsClusteredStreamLimitsCheck(acc, cfg); err != nil { + if err := js.jsClusteredStreamLimitsCheck(acc, &cfg); err != nil { resp.Error = err s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return } - if sa := js.streamAssignment(ci.serviceAccount(), cfg.Name); sa != nil { + if sa := js.streamAssignmentOrInflight(ci.serviceAccount(), cfg.Name); sa != nil { resp.Error = NewJSStreamNameExistRestoreFailedError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return } // Raft group selection and placement. - rg, err := js.createGroupForStream(ci, cfg) + rg, err := js.createGroupForStream(ci, &cfg) if err != nil { resp.Error = NewJSClusterNoPeersError(err) s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) @@ -7676,10 +8268,12 @@ func (s *Server) jsClusteredStreamRestoreRequest( } // Pick a preferred leader. rg.setPreferred(s) - sa := &streamAssignment{Group: rg, Sync: syncSubjForStream(), Config: cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} + sa := &streamAssignment{Group: rg, Sync: syncSubjForStream(), Config: &cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} // Now add in our restore state and pre-select a peer to handle the actual receipt of the snapshot. sa.Restore = &req.State - cc.meta.Propose(encodeAddStreamAssignment(sa)) + if err := cc.meta.Propose(encodeAddStreamAssignment(sa)); err == nil { + cc.trackInflightStreamProposal(ci.serviceAccount(), sa, false) + } } // Determine if all peers for this group are offline. @@ -7741,6 +8335,9 @@ func (s *Server) jsClusteredStreamListRequest(acc *Account, ci *ClientInfo, filt } scnt := len(streams) + if offset < 0 { + offset = 0 + } if offset > scnt { offset = scnt } @@ -7891,6 +8488,9 @@ func (s *Server) jsClusteredConsumerListRequest(acc *Account, ci *ClientInfo, of } ocnt := len(consumers) + if offset < 0 { + offset = 0 + } if offset > ocnt { offset = ocnt } @@ -8057,9 +8657,10 @@ func (s *Server) jsClusteredConsumerDeleteRequest(ci *ClientInfo, acc *Account, s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return } - oca.deleted = true ca := &consumerAssignment{Group: oca.Group, Stream: stream, Name: consumer, Config: oca.Config, Subject: subject, Reply: reply, Client: ci, Created: oca.Created} - cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) + if err := cc.meta.Propose(encodeDeleteConsumerAssignment(ca)); err == nil { + cc.trackInflightConsumerProposal(acc.Name, stream, ca, true) + } } func encodeMsgDelete(md *streamMsgDelete) []byte { @@ -8177,7 +8778,7 @@ func decodeStreamAssignmentConfig(s *Server, sa *streamAssignment) error { sa.Config = &cfg fixCfgMirrorWithDedupWindow(sa.Config) - if unsupported || err != nil || (sa.Config != nil && !supportsRequiredApiLevel(sa.Config.Metadata)) { + if unsupported || (sa.Config != nil && !supportsRequiredApiLevel(sa.Config.Metadata)) { sa.unsupported = newUnsupportedStreamAssignment(s, sa, err) } return nil @@ -8283,7 +8884,7 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec } // Lookup the stream assignment. - sa := js.streamAssignment(acc.Name, stream) + sa := js.streamAssignmentOrInflight(acc.Name, stream) if sa == nil { resp.Error = NewJSStreamNotFoundError() s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) @@ -8309,27 +8910,26 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec maxc = selectedLimits.MaxConsumers } if maxc > 0 { - // Don't count DIRECTS. - total := 0 - for cn, ca := range sa.consumers { - if ca.unsupported != nil { - continue - } - // If the consumer name is specified and we think it already exists, then - // we're likely updating an existing consumer, so don't count it. Otherwise - // we will incorrectly return NewJSMaximumConsumersLimitError for an update. - if oname != _EMPTY_ && cn == oname && sa.consumers[oname] != nil { - continue + // If the consumer name is specified and we think it already exists, then + // we're likely updating an existing consumer, so don't count it. Otherwise + // we will incorrectly return NewJSMaximumConsumersLimitError for an update. + if oname == _EMPTY_ || js.consumerAssignmentOrInflight(acc.Name, stream, oname) == nil { + // Don't count DIRECTS. + total := 0 + for ca := range js.consumerAssignmentsOrInflightSeq(acc.Name, stream) { + if ca.unsupported != nil { + continue + } + if ca.Config != nil && !ca.Config.Direct { + total++ + } } - if ca.Config != nil && !ca.Config.Direct { - total++ + if total >= maxc { + resp.Error = NewJSMaximumConsumersLimitError() + s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) + return } } - if total >= maxc { - resp.Error = NewJSMaximumConsumersLimitError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } } } @@ -8360,11 +8960,10 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec } var ca *consumerAssignment - // See if we have an existing one already under same durable name or // if name was set by the user. if oname != _EMPTY_ { - if ca = sa.consumers[oname]; ca != nil && !ca.deleted { + if ca = js.consumerAssignmentOrInflight(acc.Name, stream, oname); ca != nil { // Provided config might miss metadata, copy from existing config. copyConsumerMetadata(cfg, ca.Config) @@ -8426,10 +9025,8 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec // Make sure name is unique. for { oname = createConsumerName() - if sa.consumers != nil { - if sa.consumers[oname] != nil { - continue - } + if js.consumerAssignmentOrInflight(acc.Name, stream, oname) != nil { + continue } break } @@ -8467,19 +9064,16 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) return } - // Check here to make sure we have not collided with another. - if len(sa.consumers) > 0 { - for _, oca := range sa.consumers { - if oca.Name == oname { - continue - } - for _, psubj := range gatherSubjectFilters(oca.Config.FilterSubject, oca.Config.FilterSubjects) { - for _, subj := range subjects { - if SubjectsCollide(subj, psubj) { - resp.Error = NewJSConsumerWQConsumerNotUniqueError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } + for oca := range js.consumerAssignmentsOrInflightSeq(acc.Name, stream) { + if oca.Name == oname { + continue + } + for _, psubj := range gatherSubjectFilters(oca.Config.FilterSubject, oca.Config.FilterSubjects) { + for _, subj := range subjects { + if SubjectsCollide(subj, psubj) { + resp.Error = NewJSConsumerWQConsumerNotUniqueError() + s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) + return } } } @@ -8517,8 +9111,8 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec if !s.allPeersOffline(ca.Group) { // Need to release js lock. js.mu.Unlock() - if ci, err := sysRequest[ConsumerInfo](s, clusterConsumerInfoT, ci.serviceAccount(), sa.Config.Name, cfg.Durable); err != nil { - s.Warnf("Did not receive consumer info results for '%s > %s > %s' due to: %s", acc, sa.Config.Name, cfg.Durable, err) + if ci, err := sysRequest[ConsumerInfo](s, clusterConsumerInfoT, ci.serviceAccount(), sa.Config.Name, oname); err != nil { + s.Warnf("Did not receive consumer info results for '%s > %s > %s' due to: %s", acc, sa.Config.Name, oname, err) } else if ci != nil { if cl := ci.Cluster; cl != nil { curLeader = getHash(cl.Leader) @@ -8586,12 +9180,7 @@ func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subjec // Do formal proposal. if err := cc.meta.Propose(encodeAddConsumerAssignment(ca)); err == nil { - // Mark this as pending. - if sa.consumers == nil { - sa.consumers = make(map[string]*consumerAssignment) - } - ca.pending = true - sa.consumers[ca.Name] = ca + cc.trackInflightConsumerProposal(acc.Name, stream, ca, false) } } @@ -8640,7 +9229,7 @@ func decodeConsumerAssignmentConfig(ca *consumerAssignment) error { } } ca.Config = &cfg - if unsupported || err != nil || (ca.Config != nil && !supportsRequiredApiLevel(ca.Config.Metadata)) { + if unsupported || (ca.Config != nil && !supportsRequiredApiLevel(ca.Config.Metadata)) { ca.unsupported = newUnsupportedConsumerAssignment(ca, err) } return nil @@ -9377,6 +9966,7 @@ func (mset *stream) processSnapshot(snap *StreamReplicatedState, index uint64) ( defer releaseSyncOutSem() // Do not let this go on forever. + start := time.Now() const maxRetries = 3 var numRetries int @@ -9500,6 +10090,7 @@ RETRY: // We MUST ensure all data is flushed up to this point, if the store hadn't already. // Because the snapshot needs to represent what has been persisted. mset.flushAllPending() + s.Noticef("Catchup for stream '%s > %s' complete (took %v)", mset.account(), mset.name(), time.Since(start).Round(time.Millisecond)) return nil } @@ -10048,7 +10639,7 @@ func (mset *stream) runCatchup(sendSubject string, sreq *streamSyncRequest) { sendNextBatchAndContinue := func(qch chan struct{}) bool { // Check if we know we will not enter the loop because we are done. if seq > last { - s.Noticef("Catchup for stream '%s > %s' complete (took %v)", mset.account(), mset.name(), time.Since(start)) + s.Noticef("Catchup for stream '%s > %s' complete (took %v)", mset.account(), mset.name(), time.Since(start).Round(time.Millisecond)) // EOF s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, nil) return false @@ -10158,11 +10749,11 @@ func (mset *stream) runCatchup(sendSubject string, sreq *streamSyncRequest) { // when trying to recover after corruption, still not 100% sure. Could be off by 1 too somehow, // but tested a ton of those with no success. s.Warnf("Catchup for stream '%s > %s' completed (took %v), but requested sequence %d was larger than current state: %+v", - mset.account(), mset.name(), time.Since(start), seq, state) + mset.account(), mset.name(), time.Since(start).Round(time.Millisecond), seq, state) // Try our best to redo our invalidated snapshot as well. if n := mset.raftNode(); n != nil { if snap := mset.stateSnapshot(); snap != nil { - n.InstallSnapshot(snap) + n.InstallSnapshot(snap, true) } } // If we allow gap markers check if we have one pending. @@ -10204,7 +10795,7 @@ func (mset *stream) runCatchup(sendSubject string, sreq *streamSyncRequest) { if drOk && dr.First > 0 { sendDR() } - s.Noticef("Catchup for stream '%s > %s' complete (took %v)", mset.account(), mset.name(), time.Since(start)) + s.Noticef("Catchup for stream '%s > %s' complete (took %v)", mset.account(), mset.name(), time.Since(start).Round(time.Millisecond)) // EOF s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, nil) return false diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go index 8d6e9e7cab0..30e0005f4e0 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go @@ -17,7 +17,7 @@ import "strconv" const ( // JSApiLevel is the maximum supported JetStream API level for this server. - JSApiLevel int = 2 + JSApiLevel int = 3 JSRequiredLevelMetadataKey = "_nats.req.level" JSServerVersionMetadataKey = "_nats.ver" diff --git a/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go b/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go index b939f9dc1cc..bd3e26462fa 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go @@ -63,9 +63,9 @@ const ( // LEAF connection as opposed to a CLIENT. leafNodeWSPath = "/leafnode" - // This is the time the server will wait, when receiving a CONNECT, - // before closing the connection if the required minimum version is not met. - leafNodeWaitBeforeClose = 5 * time.Second + // When a soliciting leafnode is rejected because it does not meet the + // configured minimum version, delay the next reconnect attempt by this long. + leafNodeMinVersionReconnectDelay = 5 * time.Second ) type leaf struct { @@ -691,9 +691,8 @@ func (s *Server) connectToRemoteLeafNode(remote *leafNodeCfg, firstConnect bool) } else { s.Debugf("Trying to connect as leafnode to remote server on %q%s", rURL.Host, ipStr) - // Check if proxy is configured first, then check if URL supports it - if proxyURL != _EMPTY_ && isWSURL(rURL) { - // Use proxy for WebSocket connections - use original hostname, resolved IP for connection + // Check if proxy is configured + if proxyURL != _EMPTY_ { targetHost := rURL.Host // If URL doesn't include port, add the default port for the scheme if rURL.Port() == _EMPTY_ { @@ -2082,17 +2081,11 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro if mv := s.getOpts().LeafNode.MinVersion; mv != _EMPTY_ { major, minor, update, _ := versionComponents(mv) if !versionAtLeast(proto.Version, major, minor, update) { - // We are going to send back an INFO because otherwise recent - // versions of the remote server would simply break the connection - // after 2 seconds if not receiving it. Instead, we want the - // other side to just "stall" until we finish waiting for the holding - // period and close the connection below. + // Send back an INFO so recent remote servers process the rejection + // cleanly, then close immediately. The soliciting side applies the + // reconnect delay when it processes the error. s.sendPermsAndAccountInfo(c) - c.sendErrAndErr(fmt.Sprintf("connection rejected since minimum version required is %q", mv)) - select { - case <-c.srv.quitCh: - case <-time.After(leafNodeWaitBeforeClose): - } + c.sendErrAndErr(fmt.Sprintf("%s %q", ErrLeafNodeMinVersionRejected, mv)) c.closeConnection(MinimumVersionRequired) return ErrMinimumVersionRequired } @@ -2760,6 +2753,14 @@ func (c *client) processLeafSub(argo []byte) (err error) { } acc := c.acc + // Guard against LS+ arriving before CONNECT has been processed, which + // can happen when compression is enabled. + if acc == nil { + c.mu.Unlock() + c.sendErr("Authorization Violation") + c.closeConnection(ProtocolViolation) + return nil + } // Check if we have a loop. ldsPrefix := bytes.HasPrefix(sub.subject, []byte(leafNodeLoopDetectionSubjectPrefix)) @@ -2876,7 +2877,6 @@ func (c *client) processLeafUnsub(arg []byte) error { // Indicate any activity, so pub and sub or unsubs. c.in.subs++ - acc := c.acc srv := c.srv c.mu.Lock() @@ -2885,6 +2885,15 @@ func (c *client) processLeafUnsub(arg []byte) error { return nil } + acc := c.acc + // Guard against LS- arriving before CONNECT has been processed. + if acc == nil { + c.mu.Unlock() + c.sendErr("Authorization Violation") + c.closeConnection(ProtocolViolation) + return nil + } + spoke := c.isSpokeLeafNode() // We store local subs by account and subject and optionally queue name. // LS- will have the arg exactly as the key. @@ -2916,8 +2925,7 @@ func (c *client) processLeafUnsub(arg []byte) error { func (c *client) processLeafHeaderMsgArgs(arg []byte) error { // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_MSG_ARGS][]byte{} - args := a[:0] + args := c.argsa[:0] start := -1 for i, b := range arg { switch b { @@ -3000,8 +3008,7 @@ func (c *client) processLeafHeaderMsgArgs(arg []byte) error { func (c *client) processLeafMsgArgs(arg []byte) error { // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_MSG_ARGS][]byte{} - args := a[:0] + args := c.argsa[:0] start := -1 for i, b := range arg { switch b { @@ -3181,6 +3188,11 @@ func (c *client) leafProcessErr(errStr string) { c.Errorf("Leafnode connection dropped with same cluster name error. Delaying attempt to reconnect for %v", delay) return } + if strings.Contains(errStr, ErrLeafNodeMinVersionRejected.Error()) { + _, delay := c.setLeafConnectDelayIfSoliciting(leafNodeMinVersionReconnectDelay) + c.Errorf("Leafnode connection dropped due to minimum version requirement. Delaying attempt to reconnect for %v", delay) + return + } // We will look for Loop detected error coming from the other side. // If we solicit, set the connect delay. @@ -3203,7 +3215,10 @@ func (c *client) setLeafConnectDelayIfSoliciting(delay time.Duration) (string, t } c.leaf.remote.setConnectDelay(delay) } - accName := c.acc.Name + var accName string + if c.acc != nil { + accName = c.acc.Name + } c.mu.Unlock() return accName, delay } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go index 0ebde6a9f8c..63b5a9df85a 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go @@ -786,6 +786,10 @@ func (ms *memStore) allLastSeqsLocked() ([]uint64, error) { seqs := make([]uint64, 0, ms.fss.Size()) ms.fss.IterFast(func(subj []byte, ss *SimpleState) bool { + // Check if we need to recalculate. We only care about the last sequence. + if ss.lastNeedsUpdate { + ms.recalculateForSubj(bytesToString(subj), ss) + } seqs = append(seqs, ss.Last) return true }) @@ -803,6 +807,7 @@ func (ms *memStore) filterIsAll(filters []string) bool { } // Sort so we can compare. slices.Sort(filters) + slices.Sort(ms.cfg.Subjects) for i, subj := range filters { if !subjectIsSubsetMatch(ms.cfg.Subjects[i], subj) { return false @@ -814,8 +819,8 @@ func (ms *memStore) filterIsAll(filters []string) bool { // MultiLastSeqs will return a sorted list of sequences that match all subjects presented in filters. // We will not exceed the maxSeq, which if 0 becomes the store's last sequence. func (ms *memStore) MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error) { - ms.mu.RLock() - defer ms.mu.RUnlock() + ms.mu.Lock() + defer ms.mu.Unlock() if len(ms.msgs) == 0 { return nil, nil @@ -843,6 +848,9 @@ func (ms *memStore) MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed in for _, filter := range filters { ms.fss.Match(stringToBytes(filter), func(subj []byte, ss *SimpleState) { + if ss.lastNeedsUpdate { + ms.recalculateForSubj(bytesToString(subj), ss) + } if ss.Last <= maxSeq { addIfNotDupe(ss.Last) } else if ss.Msgs > 1 { @@ -1664,7 +1672,8 @@ func (ms *memStore) SubjectForSeq(seq uint64) (string, error) { return _EMPTY_, ErrStoreMsgNotFound } if sm, ok := ms.msgs[seq]; ok { - return sm.subj, nil + // Copy the subject, as it's used elsewhere, and we've released the lock in the meantime. + return copyString(sm.subj), nil } return _EMPTY_, ErrStoreMsgNotFound } @@ -1716,6 +1725,10 @@ func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error } else if subjectIsLiteral(subject) { var ss *SimpleState if ss, ok = ms.fss.Find(stringToBytes(subject)); ok && ss.Msgs > 0 { + // Check if we need to recalculate. We only care about the last sequence. + if ss.lastNeedsUpdate { + ms.recalculateForSubj(subject, ss) + } sm, ok = ms.msgs[ss.Last] } } else if ss := ms.filteredStateLocked(1, subject, true); ss.Msgs > 0 { @@ -1774,6 +1787,78 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store return ms.loadNextMsgLocked(filter, wc, start, smp) } +// Find sequence bounds matching a wildcard filter from ms.fss. +// Returns (first, last, true) if there is at least one matching +// subject at or after start (start <= first <= last). +// Returns (0, 0, false) if the subject does not exist or has no +// messages at or after start. +// Lock should be held. +func (ms *memStore) nextWildcardMatchLocked(filter string, start uint64) (uint64, uint64, bool) { + found := false + first, last := ms.state.LastSeq, uint64(0) + ms.fss.MatchUntil(stringToBytes(filter), func(subj []byte, ss *SimpleState) bool { + ms.recalculateForSubj(string(subj), ss) + + // Skip matches that are below our starting sequence + if start > ss.Last { + return true + } + + // A match was found, adjust the bounds accordingly + found = true + if ss.First < first { + first = ss.First + } + if ss.Last > last { + last = ss.Last + } + + // If first > start, there may be more matches between + // start and first, in which case we keep searching. + // If not, we have a match between start and last, we + // can break out of the search. + // This could be further optimized: if first and start + // are "close", we could just extend the linear search, + // especially if we know that the remaining ms.fss to + // explore is large. + return first > start + }) + if !found { + return 0, 0, false + } + return max(first, start), last, found +} + +// Find sequence bounds matching a literal filter from ms.fss. +// Returns (first, last, true) if there is a matching literal +// subject at or after start (start <= first <= last). +// Returns (0, 0, false) if the subject does not exist or has no +// messages at or after start. +// Lock should be held. +func (ms *memStore) nextLiteralMatchLocked(filter string, start uint64) (uint64, uint64, bool) { + ss, ok := ms.fss.Find(stringToBytes(filter)) + if !ok { + return 0, 0, false + } + ms.recalculateForSubj(filter, ss) + if start > ss.Last { + return 0, 0, false + } + return max(start, ss.First), ss.Last, true +} + +// Returns true if LoadNextMsg should perform a linear scan, +// false if it should use the subject tree to try to reduce +// the search space. +// Lock should be held. +func (ms *memStore) shouldLinearScan(filter string, wc bool, start uint64) bool { + // Skip scan of ms.fss if number of messages in the block are less than + // 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries. + const linearScanMaxFSS = 256 + isAll := filter == fwcs + return isAll || 2*int(ms.state.LastSeq-start) < ms.fss.Size() || (wc && ms.fss.Size() > linearScanMaxFSS) +} + // Lock should be held. func (ms *memStore) loadNextMsgLocked(filter string, wc bool, start uint64, smp *StoreMsg) (*StoreMsg, uint64, error) { if start < ms.state.FirstSeq { @@ -1790,46 +1875,24 @@ func (ms *memStore) loadNextMsgLocked(filter string, wc bool, start uint64, smp } isAll := filter == fwcs - // Skip scan of ms.fss if number of messages in the block are less than - // 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries. - const linearScanMaxFSS = 256 - doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < ms.fss.Size() || (wc && ms.fss.Size() > linearScanMaxFSS) - // Initial setup. fseq, lseq := start, ms.state.LastSeq - if !doLinearScan { - subs := []string{filter} - if wc || isAll { - subs = subs[:0] - ms.fss.Match(stringToBytes(filter), func(subj []byte, val *SimpleState) { - subs = append(subs, string(subj)) - }) - } - fseq, lseq = ms.state.LastSeq, uint64(0) - for _, subj := range subs { - ss, ok := ms.fss.Find(stringToBytes(subj)) - if !ok { - continue - } - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - ms.recalculateForSubj(subj, ss) - } - if ss.First < fseq { - fseq = ss.First - } - if ss.Last > lseq { - lseq = ss.Last - } + if !ms.shouldLinearScan(filter, wc, start) { + var found bool + if wc { + fseq, lseq, found = ms.nextWildcardMatchLocked(filter, start) + } else { + fseq, lseq, found = ms.nextLiteralMatchLocked(filter, start) } - if fseq < start { - fseq = start + if !found { + return nil, ms.state.LastSeq, ErrStoreEOF } } eq := subjectsEqual if wc { - eq = subjectIsSubsetMatch + eq = matchLiteral } for nseq := fseq; nseq <= lseq; nseq++ { @@ -2113,8 +2176,8 @@ func (ms *memStore) FastState(state *StreamState) { } func (ms *memStore) State() StreamState { - ms.mu.RLock() - defer ms.mu.RUnlock() + ms.mu.Lock() + defer ms.mu.Unlock() state := ms.state state.Consumers = ms.consumers @@ -2336,7 +2399,7 @@ func (o *consumerMemStore) Update(state *ConsumerState) error { // Check to see if this is an outdated update. if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream { - return fmt.Errorf("old update ignored") + return ErrStoreOldUpdate } o.state.Delivered = state.Delivered diff --git a/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go b/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go index 87bfd551065..2ca0230788c 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go @@ -1,4 +1,4 @@ -// Copyright 2020-2025 The NATS Authors +// Copyright 2020-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -241,6 +241,7 @@ var ( errMQTTUnsupportedCharacters = errors.New("character ' ' not supported for MQTT topics") errMQTTInvalidSession = errors.New("invalid MQTT session") errMQTTInvalidRetainFlags = errors.New("invalid retained message flags") + errMQTTSessionCollision = errors.New("stored session does not match client ID") ) type srvMQTT struct { @@ -260,7 +261,7 @@ type mqttAccountSessionManager struct { sessions map[string]*mqttSession // key is MQTT client ID sessByHash map[string]*mqttSession // key is MQTT client ID hash sessLocked map[string]struct{} // key is MQTT client ID and indicate that a session can not be taken by a new client at this time - flappers map[string]int64 // When connection connects with client ID already in use + flappers map[string]time.Time // When connection connects with client ID already in use flapTimer *time.Timer // Timer to perform some cleanup of the flappers map sl *Sublist // sublist allowing to find retained messages for given subscription retmsgs map[string]*mqttRetainedMsgRef // retained messages @@ -585,7 +586,7 @@ func (s *Server) createMQTTClient(conn net.Conn, ws *websocket) *client { return c } - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { + if opts.MaxConn < 0 || (opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn) { s.mu.Unlock() c.maxConnExceeded() return nil @@ -789,11 +790,17 @@ func (c *client) mqttParse(buf []byte) error { } break } + if err = mqttCheckFixedHeaderFlags(pt, b&mqttPacketFlagMask); err != nil { + break + } pl, complete, err = r.readPacketLen() if err != nil || !complete { break } + if err = mqttCheckRemainingLength(pt, pl); err != nil { + break + } switch pt { // Packets that we receive back when we act as the "sender": PUBACK, @@ -958,6 +965,43 @@ func (c *client) mqttParse(buf []byte) error { return err } +func mqttCheckFixedHeaderFlags(packetType, flags byte) error { + var expected byte + switch packetType { + case mqttPacketConnect, mqttPacketPubAck, mqttPacketPubRec, mqttPacketPubComp, + mqttPacketPing, mqttPacketDisconnect: + expected = 0 + case mqttPacketPubRel, mqttPacketSub, mqttPacketUnsub: + expected = 0x2 + case mqttPacketPub: + return nil + default: + return nil + } + if flags != expected { + return fmt.Errorf("invalid fixed header flags %x for packet type %x", flags, packetType) + } + return nil +} + +func mqttCheckRemainingLength(packetType byte, pl int) error { + var expected int + switch packetType { + case mqttPacketConnect, mqttPacketPub, mqttPacketSub, mqttPacketUnsub: + return nil + case mqttPacketPubAck, mqttPacketPubRec, mqttPacketPubRel, mqttPacketPubComp: + expected = 2 + case mqttPacketPing, mqttPacketDisconnect: + expected = 0 + default: + return nil + } + if pl != expected { + return fmt.Errorf("invalid remaining length %d for packet type %x", pl, packetType) + } + return nil +} + func (c *client) mqttTraceMsg(msg []byte) { maxTrace := c.srv.getOpts().MaxTracedMsgLen if maxTrace > 0 && len(msg) > maxTrace { @@ -1174,7 +1218,7 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc sessions: make(map[string]*mqttSession), sessByHash: make(map[string]*mqttSession), sessLocked: make(map[string]struct{}), - flappers: make(map[string]int64), + flappers: make(map[string]time.Time), jsa: mqttJSA{ id: id, c: c, @@ -2090,7 +2134,7 @@ func (as *mqttAccountSessionManager) processSessionPersist(_ *subscription, pc * // // Lock held on entry. func (as *mqttAccountSessionManager) addSessToFlappers(clientID string) { - as.flappers[clientID] = time.Now().UnixNano() + as.flappers[clientID] = time.Now() if as.flapTimer == nil { as.flapTimer = time.AfterFunc(mqttFlapCleanItvl, func() { as.mu.Lock() @@ -2099,9 +2143,9 @@ func (as *mqttAccountSessionManager) addSessToFlappers(clientID string) { if as.flapTimer == nil { return } - now := time.Now().UnixNano() + now := time.Now() for cID, tm := range as.flappers { - if now-tm > int64(mqttSessJailDur) { + if now.Sub(tm) > mqttSessJailDur { delete(as.flappers, cID) } } @@ -2971,16 +3015,12 @@ func mqttDecodeRetainedMessage(subject string, h, m []byte) (*mqttRetainedMsg, e // Lock not held on entry, but session is in the locked map. func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opts *Options) (*mqttSession, bool, error) { jsa := &as.jsa - formatError := func(errTxt string, err error) (*mqttSession, bool, error) { - accName := jsa.c.acc.GetName() - return nil, false, fmt.Errorf("%s for account %q, session %q: %v", errTxt, accName, clientID, err) - } hash := getHash(clientID) smsg, err := jsa.loadSessionMsg(as.domainTk, hash) if err != nil { if isErrorOtherThan(err, JSNoMessageFoundErr) { - return formatError("loading session record", err) + return nil, false, fmt.Errorf("loading session record: %w", err) } // Message not found, so reate the session... // Create a session and indicate that this session did not exist. @@ -2991,7 +3031,10 @@ func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opt // We need to recover the existing record now. ps := &mqttPersistedSession{} if err := json.Unmarshal(smsg.Data, ps); err != nil { - return formatError(fmt.Sprintf("unmarshal of session record at sequence %v", smsg.Sequence), err) + return nil, false, fmt.Errorf("unmarshal of session record at sequence %v: %w", smsg.Sequence, err) + } + if ps.ID != clientID { + return nil, false, errMQTTSessionCollision } // Restore this session (even if we don't own it), the caller will do the right thing. @@ -3673,8 +3716,12 @@ func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttC c.mqtt.cid = nuid.Next() } // Spec [MQTT-3.1.3-4] and [MQTT-3.1.3-9] - if !utf8.ValidString(c.mqtt.cid) { - return mqttConnAckRCIdentifierRejected, nil, fmt.Errorf("invalid utf8 for client ID: %q", c.mqtt.cid) + if err := mqttValidateString(c.mqtt.cid, "client ID"); err != nil { + return mqttConnAckRCIdentifierRejected, nil, err + } else if !isValidName(c.mqtt.cid) { + // Should not contain characters that make it an invalid name for NATS subjects, etc. + err = fmt.Errorf("invalid character in %s %q", "client ID", c.mqtt.cid) + return mqttConnAckRCIdentifierRejected, nil, err } if hasWill { @@ -3692,8 +3739,8 @@ func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttC if len(topic) == 0 { return 0, nil, errMQTTEmptyWillTopic } - if !utf8.Valid(topic) { - return 0, nil, fmt.Errorf("invalid utf8 for Will topic %q", topic) + if err := mqttValidateTopic(topic, "Will topic"); err != nil { + return 0, nil, err } // Convert MQTT topic to NATS subject cp.will.subject, err = mqttTopicToNATSPubSubject(topic) @@ -3734,8 +3781,8 @@ func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttC return mqttConnAckRCBadUserOrPassword, nil, errMQTTEmptyUsername } // Spec [MQTT-3.1.3-11] - if !utf8.ValidString(c.opts.Username) { - return mqttConnAckRCBadUserOrPassword, nil, fmt.Errorf("invalid utf8 for user name %q", c.opts.Username) + if err := mqttValidateString(c.opts.Username, "user name"); err != nil { + return mqttConnAckRCBadUserOrPassword, nil, err } } @@ -3745,7 +3792,6 @@ func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttC return 0, nil, err } c.opts.Token = c.opts.Password - c.opts.JWT = c.opts.Password } return 0, cp, nil } @@ -3835,7 +3881,7 @@ CHECK: if tm, ok := asm.flappers[cid]; ok { // If the last time it tried to connect was more than 1 sec ago, // then accept and remove from flappers map. - if time.Now().UnixNano()-tm > int64(mqttSessJailDur) { + if time.Since(tm) > mqttSessJailDur { asm.removeSessFromFlappers(cid) } else { // Will hold this client for a second and then close it. We @@ -3883,13 +3929,19 @@ CHECK: // Do we have an existing session for this client ID es, exists := asm.sessions[cid] asm.mu.Unlock() + formatError := func(err error) error { + return fmt.Errorf("%v for account %q, session %q", err, c.acc.GetName(), cid) + } // The session is not in the map, but may be on disk, so try to recover // or create the stream if not. if !exists { es, exists, err = asm.createOrRestoreSession(cid, s.getOpts()) if err != nil { - return err + if err == errMQTTSessionCollision { + sendConnAck(mqttConnAckRCIdentifierRejected, false) + } + return formatError(err) } } if exists { @@ -4041,6 +4093,9 @@ func (c *client) mqttParsePub(r *mqttReader, pl int, pp *mqttPublish, hasMapping if len(pp.topic) == 0 { return errMQTTTopicIsEmpty } + if err := mqttValidateTopic(pp.topic, "topic"); err != nil { + return err + } // Convert the topic to a NATS subject. This call will also check that // there is no MQTT wildcards (Spec [MQTT-3.3.2-2] and [MQTT-4.7.1-1]) // Note that this may not result in a copy if there is no conversion. @@ -4093,6 +4148,26 @@ func (c *client) mqttParsePub(r *mqttReader, pl int, pp *mqttPublish, hasMapping return nil } +func mqttValidateTopic(topic []byte, field string) error { + if !utf8.Valid(topic) { + return fmt.Errorf("invalid utf8 for %s %q", field, topic) + } + if bytes.IndexByte(topic, 0) >= 0 { + return fmt.Errorf("invalid null character in %s %q", field, topic) + } + return nil +} + +func mqttValidateString(value string, field string) error { + if !utf8.ValidString(value) { + return fmt.Errorf("invalid utf8 for %s %q", field, value) + } + if strings.IndexByte(value, 0) >= 0 { + return fmt.Errorf("invalid null character in %s %q", field, value) + } + return nil +} + func mqttPubTrace(pp *mqttPublish) string { dup := pp.flags&mqttPubFlagDup != 0 qos := mqttGetQoS(pp.flags) @@ -4774,9 +4849,9 @@ func (c *client) mqttParseSubsOrUnsubs(r *mqttReader, b byte, pl int, sub bool) if rf := b & 0xf; rf != expectedFlag { return 0, nil, fmt.Errorf("wrong %ssubscribe reserved flags: %x", action, rf) } - pi, err := r.readUint16("packet identifier") + pi, err := mqttParsePIPacket(r) if err != nil { - return 0, nil, fmt.Errorf("reading packet identifier: %v", err) + return 0, nil, err } end := r.pos + (pl - 2) var filters []*mqttFilter @@ -4791,8 +4866,8 @@ func (c *client) mqttParseSubsOrUnsubs(r *mqttReader, b byte, pl int, sub bool) return 0, nil, errMQTTTopicFilterCannotBeEmpty } // Spec [MQTT-3.8.3-1], [MQTT-3.10.3-1] - if !utf8.Valid(topic) { - return 0, nil, fmt.Errorf("invalid utf8 for topic filter %q", topic) + if err := mqttValidateTopic(topic, "topic filter"); err != nil { + return 0, nil, err } var qos byte // We are going to report if we had an error during the conversion, diff --git a/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go b/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go index 3f995f069ce..1cbb6dcbcea 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go @@ -367,6 +367,13 @@ func (c *client) initMsgTrace() *msgTrace { } } dest = getHdrVal(MsgTraceDest) + if c.kind == CLIENT { + if td, ok := c.allowedMsgTraceDest(hdr, false); !ok { + return nil + } else if td != _EMPTY_ { + dest = td + } + } // Check the destination to see if this is a valid public subject. if !IsValidPublishSubject(dest) { // We still have to return a msgTrace object (if traceOnly is set) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/opts.go b/vendor/github.com/nats-io/nats-server/v2/server/opts.go index f989fd530cc..3828aa66d30 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/opts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/opts.go @@ -389,6 +389,7 @@ type Options struct { JetStreamRequestQueueLimit int64 JetStreamMetaCompact uint64 JetStreamMetaCompactSize uint64 + JetStreamMetaCompactSync bool StreamMaxBufferedMsgs int `json:"-"` StreamMaxBufferedSize int64 `json:"-"` StoreDir string `json:"-"` @@ -1268,7 +1269,9 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin case "proxy_protocol": o.ProxyProtocol = v.(bool) case "max_connections", "max_conn": - o.MaxConn = int(v.(int64)) + if o.MaxConn = int(v.(int64)); o.MaxConn == 0 { + o.MaxConn = -1 + } case "max_traced_msg_len": o.MaxTracedMsgLen = int(v.(int64)) case "max_subscriptions", "max_subs": @@ -2653,6 +2656,8 @@ func parseJetStream(v any, opts *Options, errors *[]error, warnings *[]error) er return &configErr{tk, fmt.Sprintf("Expected an absolute size for %q, got %v", mk, mv)} } opts.JetStreamMetaCompactSize = uint64(s) + case "meta_compact_sync": + opts.JetStreamMetaCompactSync = mv.(bool) default: if !tk.IsUsedVariable() { err := &unknownConfigFieldErr{ @@ -6430,3 +6435,18 @@ func expandPath(p string) (string, error) { return filepath.Join(home, p[1:]), nil } + +// RedactArgs redacts sensitive arguments from the command line. +// For example, turns '--pass=secret' into '--pass=[REDACTED]'. +func RedactArgs(args []string) { + secret := regexp.MustCompile("^-{1,2}(user|pass|auth)(=.*)?$") + for i, arg := range args { + if secret.MatchString(arg) { + if idx := strings.Index(arg, "="); idx != -1 { + args[i] = arg[:idx] + "=[REDACTED]" + } else if i+1 < len(args) { + args[i+1] = "[REDACTED]" + } + } + } +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/parser.go b/vendor/github.com/nats-io/nats-server/v2/server/parser.go index 58d034a6b9f..ef25e093134 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/parser.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/parser.go @@ -32,6 +32,7 @@ type parseState struct { msgBuf []byte header http.Header // access via getHeader scratch [MAX_CONTROL_LINE_SIZE]byte + argsa [MAX_HMSG_ARGS + 1][]byte // pre-allocated args array to avoid per-call heap escape } type pubArg struct { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/proto.go b/vendor/github.com/nats-io/nats-server/v2/server/proto.go index 9843fff21b4..f757a09e2db 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/proto.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/proto.go @@ -63,8 +63,14 @@ func protoScanFieldValue(typ int, b []byte) (size int, err error) { case 0: _, size, err = protoScanVarint(b) case 5: // fixed32 + if len(b) < 4 { + return 0, errProtoInsufficient + } size = 4 case 1: // fixed64 + if len(b) < 8 { + return 0, errProtoInsufficient + } size = 8 case 2: // length-delimited size, err = protoScanBytes(b) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/raft.go b/vendor/github.com/nats-io/nats-server/v2/server/raft.go index 5e242093738..a437b985049 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/raft.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/raft.go @@ -19,6 +19,7 @@ import ( "encoding/binary" "errors" "fmt" + "iter" "math" "math/rand" "net" @@ -40,7 +41,8 @@ type RaftNode interface { Propose(entry []byte) error ProposeMulti(entries []*Entry) error ForwardProposal(entry []byte) error - InstallSnapshot(snap []byte) error + InstallSnapshot(snap []byte, force bool) error + CreateSnapshotCheckpoint(force bool) (RaftNodeCheckpoint, error) SendSnapshot(snap []byte) error NeedSnapshot() bool Applied(index uint64) (entries uint64, bytes uint64) @@ -89,6 +91,17 @@ type RaftNode interface { GetTrafficAccountName() string } +// RaftNodeCheckpoint is used as an alternative to a direct InstallSnapshot. +// A checkpoint is created from CreateSnapshotCheckpoint and allows installing snapshots asynchronously, +// as well as loading the last snapshot or entries between the last snapshot and the one we're about to create. +// Abort can be called to cancel the snapshot installation at any time, or InstallSnapshot to install it. +type RaftNodeCheckpoint interface { + LoadLastSnapshot() (snap []byte, err error) + AppendEntriesSeq() iter.Seq2[*appendEntry, error] + Abort() + InstallSnapshot(data []byte) (uint64, error) +} + type WAL interface { Type() StorageType StoreMsg(subj string, hdr, msg []byte, ttl int64) (uint64, int64, error) @@ -179,6 +192,8 @@ type raft struct { applied uint64 // Index of the most recently applied commit papplied uint64 // First sequence of our log, matches when we last installed a snapshot. + membChangeIndex uint64 // Index of uncommitted membership change entry (0 means no change in progress) + aflr uint64 // Index when to signal initial messages have been applied after becoming leader. 0 means signaling is disabled. leader string // The ID of the leader @@ -231,8 +246,8 @@ type raft struct { observer bool // The node is observing, i.e. not able to become leader initializing bool // The node is new, and "empty log" checks can be temporarily relaxed. scaleUp bool // The node is part of a scale up, puts us in observer mode until the log contains data. - membChanging bool // There is a membership change proposal in progress deleted bool // If the node was deleted. + snapshotting bool // Snapshot is in progress. } type proposedEntry struct { @@ -240,7 +255,7 @@ type proposedEntry struct { reply string // Optional, to respond once proposal handled } -// cacthupState structure that holds our subscription, and catchup term and index +// catchupState structure that holds our subscription, and catchup term and index // as well as starting term and index and how many updates we have seen. type catchupState struct { sub *subscription // Subscription that catchup messages will arrive on @@ -249,6 +264,7 @@ type catchupState struct { pterm uint64 // Starting term pindex uint64 // Starting index active time.Time // Last time we received a message for this catchup + signal bool // Whether the EntryCatchup signal was sent. } // lps holds peer state of last time and last index replicated. @@ -311,6 +327,8 @@ var ( errNodeRemoved = errors.New("raft: peer was removed") errBadSnapName = errors.New("raft: snapshot name could not be parsed") errNoSnapAvailable = errors.New("raft: no snapshot available") + errSnapInProgress = errors.New("raft: snapshot is already in progress") + errSnapAborted = errors.New("raft: snapshot was aborted") errCatchupsRunning = errors.New("raft: snapshot can not be installed while catchups running") errSnapshotCorrupt = errors.New("raft: snapshot corrupt") errTooManyPrefs = errors.New("raft: stepdown requires at most one preferred new leader") @@ -390,6 +408,19 @@ func (s *Server) bootstrapRaftNode(cfg *RaftConfig, knownPeers []string, allPeer // initRaftNode will initialize the raft node, to be used by startRaftNode or when testing to not run the Go routine. func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabels) (*raft, error) { + restorePeerState := func(n *raft) error { + ps, err := readPeerState(cfg.Store) + if err != nil { + return err + } + if ps == nil { + return errNoPeerState + } + n.processPeerState(ps) + n.extSt = ps.domainExt + return nil + } + if cfg == nil { return nil, errNilCfg } @@ -401,15 +432,6 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel hash := s.sys.shash s.mu.RUnlock() - // Do this here to process error quicker. - ps, err := readPeerState(cfg.Store) - if err != nil { - return nil, err - } - if ps == nil { - return nil, errNoPeerState - } - qpfx := fmt.Sprintf("[ACC:%s] RAFT '%s' ", accName, cfg.Name) n := &raft{ created: time.Now(), @@ -419,8 +441,6 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel wal: cfg.Log, wtype: cfg.Log.Type(), track: cfg.Track, - csz: ps.clusterSize, - qn: ps.clusterSize/2 + 1, peers: make(map[string]*lps), acks: make(map[uint64]map[string]struct{}), pae: make(map[uint64]*appendEntry), @@ -436,7 +456,6 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel accName: accName, leadc: make(chan bool, 32), observer: cfg.Observer, - extSt: ps.domainExt, } // Setup our internal subscriptions for proposals, votes and append entries. @@ -473,6 +492,15 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel n.setupLastSnapshot() } + // We may have restored the peer state from the + // snapshot above. If not, we restore peers from + // the peer state file. + if len(n.peers) == 0 { + if err := restorePeerState(n); err != nil { + return nil, err + } + } + // Make sure that the snapshots directory exists. if err := os.MkdirAll(filepath.Join(n.sd, snapshotsDir), defaultDirPerms); err != nil { return nil, fmt.Errorf("could not create snapshots directory - %v", err) @@ -532,18 +560,7 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel } } - // Make sure to track ourselves. - n.peers[n.id] = &lps{time.Now(), 0, true} - - // Track known peers - for _, peer := range ps.knownPeers { - if peer != n.id { - // Set these to 0 to start but mark as known peer. - n.peers[peer] = &lps{time.Time{}, 0, true} - } - } - - n.debug("Started") + n.debug("Started (cluster size %d, quorum %d)", n.csz, n.qn) // Check if we need to start in observer mode due to lame duck status. // This will stop us from taking on the leader role when we're about to @@ -926,24 +943,23 @@ func (n *raft) ForwardProposal(entry []byte) error { // ProposeAddPeer is called to add a peer to the group. func (n *raft) ProposeAddPeer(peer string) error { - n.Lock() + n.RLock() // Check state under lock, we might not be leader anymore. if n.State() != Leader { - n.Unlock() + n.RUnlock() return errNotLeader } // Error if we had a previous write error. if werr := n.werr; werr != nil { - n.Unlock() + n.RUnlock() return werr } - if n.membChanging { - n.Unlock() + if n.membChangeIndex > 0 { + n.RUnlock() return errMembershipChange } prop := n.prop - n.membChanging = true - n.Unlock() + n.RUnlock() prop.push(newProposedEntry(newEntry(EntryAddPeer, []byte(peer)), _EMPTY_)) return nil @@ -951,36 +967,35 @@ func (n *raft) ProposeAddPeer(peer string) error { // ProposeRemovePeer is called to remove a peer from the group. func (n *raft) ProposeRemovePeer(peer string) error { - n.Lock() + n.RLock() // Error if we had a previous write error. if werr := n.werr; werr != nil { - n.Unlock() + n.RUnlock() return werr } if n.State() != Leader { subj := n.rpsubj - n.Unlock() + n.RUnlock() // Forward the proposal to the leader n.sendRPC(subj, _EMPTY_, []byte(peer)) return nil } - if n.membChanging { - n.Unlock() + if n.membChangeIndex > 0 { + n.RUnlock() return errMembershipChange } if len(n.peers) <= 1 { - n.Unlock() + n.RUnlock() return errRemoveLastNode } prop := n.prop - n.membChanging = true - n.Unlock() + n.RUnlock() prop.push(newProposedEntry(newEntry(EntryRemovePeer, []byte(peer)), _EMPTY_)) return nil @@ -989,7 +1004,7 @@ func (n *raft) ProposeRemovePeer(peer string) error { func (n *raft) MembershipChangeInProgress() bool { n.RLock() defer n.RUnlock() - return n.membChanging + return n.membChangeIndex > 0 } // ClusterSize reports back the total cluster size. @@ -1260,77 +1275,257 @@ func (n *raft) SendSnapshot(data []byte) error { // Used to install a snapshot for the given term and applied index. This will release // all of the log entries up to and including index. This should not be called with // entries that have been applied to the FSM but have not been applied to the raft state. -func (n *raft) InstallSnapshot(data []byte) error { - if n.State() == Closed { - return errNodeClosed +func (n *raft) InstallSnapshot(data []byte, force bool) error { + n.Lock() + defer n.Unlock() + + c, err := n.createSnapshotCheckpointLocked(force) + if err != nil { + return err + } + c.n.debug("Installing snapshot of %d bytes [%d:%d]", len(data), c.term, c.applied) + snap := &snapshot{ + lastTerm: c.term, + lastIndex: c.applied, + peerstate: c.peerstate, + data: data, + } + return c.n.installSnapshot(snap) +} + +// Install the snapshot. +// Lock should be held. +func (n *raft) installSnapshot(snap *snapshot) error { + // Always reset, regardless of success or error. + // This is done even though this doesn't come from a checkpoint. We do this so we can + // interrupt/abort an asynchronously running snapshot (if it exists). Ensures the upper layer + // can't overwrite a snapshot that we installed here with an old asynchronously created one. + defer func() { + n.snapshotting = false + }() + + snapDir := filepath.Join(n.sd, snapshotsDir) + sn := fmt.Sprintf(snapFileT, snap.lastTerm, snap.lastIndex) + sfile := filepath.Join(snapDir, sn) + + if err := writeFileWithSync(sfile, n.encodeSnapshot(snap), defaultFilePerms); err != nil { + // We could set write err here, but if this is a temporary situation, too many open files etc. + // we want to retry and snapshots are not fatal. + return err + } + + // Delete our previous snapshot file if it exists. + if n.snapfile != _EMPTY_ && n.snapfile != sfile { + os.Remove(n.snapfile) + } + // Remember our latest snapshot file. + n.snapfile = sfile + if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { + n.setWriteErrLocked(err) + return err } + var state StreamState + n.wal.FastState(&state) + n.papplied = snap.lastIndex + n.bytes = state.Bytes + return nil +} + +// CreateSnapshotCheckpoint creates a checkpoint to allow installing a snapshot asynchronously. +// Caller MUST make sure it only ever has one checkpoint handle at most, and either installs or +// aborts the checkpoint. +// See also: RaftNodeCheckpoint +func (n *raft) CreateSnapshotCheckpoint(force bool) (RaftNodeCheckpoint, error) { n.Lock() defer n.Unlock() + return n.createSnapshotCheckpointLocked(force) +} + +func (n *raft) createSnapshotCheckpointLocked(force bool) (*checkpoint, error) { + if n.State() == Closed { + return nil, errNodeClosed + } + if n.snapshotting { + return nil, errSnapInProgress + } // If a write error has occurred already then stop here. if werr := n.werr; werr != nil { - return werr + return nil, werr } // Check that a catchup isn't already taking place. If it is then we won't // allow installing snapshots until it is done. - if len(n.progress) > 0 || n.paused { - return errCatchupsRunning + // Unless we're forced to snapshot. We might have been catching up a peer for + // a long period, and this protects our log size from growing indefinitely. + if !force && len(n.progress) > 0 { + return nil, errCatchupsRunning } if n.applied == 0 { n.debug("Not snapshotting as there are no applied entries") - return errNoSnapAvailable + return nil, errNoSnapAvailable } var term uint64 if ae, _ := n.loadEntry(n.applied); ae != nil { term = ae.term + ae.returnToPool() } else { n.debug("Not snapshotting as entry %d is not available", n.applied) - return errNoSnapAvailable + return nil, errNoSnapAvailable } - n.debug("Installing snapshot of %d bytes [%d:%d]", len(data), term, n.applied) + // Snapshot the current peer state for the current applied index, we'll need it in the snapshot. + peerstate := encodePeerState(&peerState{n.peerNames(), n.csz, n.extSt}) + snapDir := filepath.Join(n.sd, snapshotsDir) + snapFile := filepath.Join(snapDir, fmt.Sprintf(snapFileT, term, n.applied)) + + n.snapshotting = true + c := &checkpoint{ + n: n, + term: term, + applied: n.applied, + papplied: n.papplied, + snapFile: snapFile, + peerstate: peerstate, + } + return c, nil +} + +type checkpoint struct { + n *raft // Reference to the RaftNode. + term uint64 // The term of the entry at applied. + applied uint64 // What applied value the snapshot will represent and what the log can be compacted to. + papplied uint64 // Previous applied value of the previous snapshot. + snapFile string // Where the snapshot should be installed. + peerstate []byte // Encoded peerstate generated when creating this checkpoint. +} + +// LoadLastSnapshot loads the last snapshot from disk when using a RaftNodeCheckpoint. +func (c *checkpoint) LoadLastSnapshot() ([]byte, error) { + c.n.Lock() + defer c.n.Unlock() + if !c.n.snapshotting { + // The checkpoint can be aborted at any time, don't continue if that happened. + return nil, errSnapAborted + } + snap, err := c.n.loadLastSnapshot() + if err != nil { + return nil, err + } + if snap.lastIndex != c.papplied { + // Another snapshot was installed in the meantime. This invalidates our checkpoint. + return nil, errors.New("snapshot index mismatch") + } + return snap.data, nil +} - return n.installSnapshot(&snapshot{ - lastTerm: term, - lastIndex: n.applied, - peerstate: encodePeerState(&peerState{n.peerNames(), n.csz, n.extSt}), - data: data, - }) +// AppendEntriesSeq allows iterating over entries that can be compacted as part of a snapshot. +func (c *checkpoint) AppendEntriesSeq() iter.Seq2[*appendEntry, error] { + return func(yield func(*appendEntry, error) bool) { + for index := c.papplied + 1; index <= c.applied; index++ { + c.n.Lock() + if !c.n.snapshotting { + c.n.Unlock() + // The checkpoint can be aborted at any time, don't continue if that happened. + yield(nil, errSnapAborted) + return + } + // Load entry and yield to the caller while unlocked. + ae, err := c.n.loadEntry(index) + c.n.Unlock() + if err != nil { + yield(nil, err) + return + } + yield(ae, nil) + ae.returnToPool() + } + } } -// Install the snapshot. +// Abort can be called to cancel the snapshot installation at any time. +func (c *checkpoint) Abort() { + c.n.Lock() + defer c.n.Unlock() + c.n.snapshotting = false +} + +// InstallSnapshot allows asynchronous installation of a snapshot by unlocking when +// performing operations that don't strictly need to be locked. When the lock is re-acquired +// n.snapshotting will be checked to ensure we're still meant to. +// Async snapshots can only be used when using CreateSnapshotCheckpoint. // Lock should be held. -func (n *raft) installSnapshot(snap *snapshot) error { - snapDir := filepath.Join(n.sd, snapshotsDir) - sn := fmt.Sprintf(snapFileT, snap.lastTerm, snap.lastIndex) - sfile := filepath.Join(snapDir, sn) +func (c *checkpoint) InstallSnapshot(data []byte) (uint64, error) { + n := c.n + n.Lock() + defer n.Unlock() + if !n.snapshotting { + // The checkpoint can be aborted at any time, don't continue if that happened. + return 0, errSnapAborted + } - if err := writeFileWithSync(sfile, n.encodeSnapshot(snap), defaultFilePerms); err != nil { + // Always reset, regardless of success or error. + defer func() { + n.snapshotting = false + }() + + n.debug("Installing snapshot of %d bytes [%d:%d]", len(data), c.term, c.applied) + snap := &snapshot{ + lastTerm: c.term, + lastIndex: c.applied, + peerstate: c.peerstate, + data: data, + } + encoded := n.encodeSnapshot(snap) + + // Unlock while writing. + n.Unlock() + err := writeFileWithSync(c.snapFile, encoded, defaultFilePerms) + n.Lock() + if err != nil { // We could set write err here, but if this is a temporary situation, too many open files etc. // we want to retry and snapshots are not fatal. - return err + return 0, err + } else if !n.snapshotting { + // The checkpoint can be aborted at any time, don't continue if that happened. + return 0, errSnapAborted } // Delete our previous snapshot file if it exists. - if n.snapfile != _EMPTY_ && n.snapfile != sfile { + if n.snapfile != _EMPTY_ && n.snapfile != c.snapFile { os.Remove(n.snapfile) } // Remember our latest snapshot file. - n.snapfile = sfile - if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { + n.snapfile = c.snapFile + + // Unlock while compacting. + n.Unlock() + _, err = n.wal.Compact(snap.lastIndex + 1) + n.Lock() + if err != nil { n.setWriteErrLocked(err) - return err + return 0, err + } else if !n.snapshotting { + // The checkpoint can be aborted at any time, don't continue if that happened. + return 0, errSnapAborted } + compacted := n.bytes var state StreamState n.wal.FastState(&state) n.papplied = snap.lastIndex n.bytes = state.Bytes - return nil + + // Expose compacted size. + if n.bytes > compacted { + compacted = 0 + } else { + compacted -= n.bytes + } + return compacted, nil } // NeedSnapshot returns true if it is necessary to try to install a snapshot, i.e. @@ -1430,6 +1625,14 @@ func (n *raft) setupLastSnapshot() { // Applied will move up when the snapshot is actually applied. n.commit = snap.lastIndex n.papplied = snap.lastIndex + // Restore the peerState + ps, err := decodePeerState(snap.peerstate) + if err == nil { + n.processPeerState(ps) + } + n.processPeerState(ps) + n.extSt = ps.domainExt + n.apply.push(newCommittedEntry(n.commit, []*Entry{{EntrySnapshot, snap.data}})) if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { n.setWriteErrLocked(err) @@ -1606,6 +1809,10 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool { n.Unlock() time.Sleep(time.Millisecond) n.Lock() + if n.State() == Closed { + n.debug("Node closed during health check, returning not current") + return false + } if n.commit-n.applied < startDelta { // The gap is getting smaller, so we're making forward progress. clearBehindState() @@ -1844,13 +2051,34 @@ func (n *raft) Peers() []*Peer { var peers []*Peer for id, ps := range n.peers { + var current bool var lag uint64 - if n.commit > ps.li { - lag = n.commit - ps.li + if id == n.id { + // We are current and have no lag when compared with ourselves. + current = true + } else if n.id == n.leader { + // We are the leader, we know how many entries this replica has persisted. + // Lag is determined by how many entries we have quorum on in our log that haven't yet + // been persisted on the replica. They are current if there's no lag. + // This will show all peers that are part of quorum as "current". + if n.commit > ps.li { + lag = n.commit - ps.li + } + current = lag == 0 + } else if id == n.leader { + // This peer is the leader, we don't know our lag, but we can report + // on whether we've seen the leader recently. + okInterval := hbInterval * 2 + current = time.Since(ps.ts) <= okInterval + } else { + // The remaining condition is another follower that we're not in contact with. + // We intentionally leave current and lag as empty. + current, lag = false, 0 } + p := &Peer{ ID: id, - Current: id == n.leader || ps.li >= n.applied, + Current: current, Last: ps.ts, Lag: lag, } @@ -2192,7 +2420,7 @@ func (n *raft) setObserverLocked(isObserver bool, extSt extensionState) { // If we're leaving observer state then reset the election timer or // we might end up waiting for up to the observerModeInterval. if wasObserver && !isObserver { - n.resetElect(randCampaignTimeout()) + n.resetElect(randElectionTimeout()) } } @@ -2610,18 +2838,23 @@ func (n *raft) handleForwardedRemovePeerProposal(sub *subscription, c *client, _ n.RLock() // Check state under lock, we might not be leader anymore. - if n.State() != Leader { + if n.State() != Leader || !n.leaderState.Load() { n.debug("Ignoring forwarded peer removal proposal, not leader") n.RUnlock() return } - prop, werr := n.prop, n.werr - n.RUnlock() - - // Ignore if we have had a write error previous. - if werr != nil { + // Error if we had a previous write error. + if werr := n.werr; werr != nil { + n.RUnlock() + return + } + if n.membChangeIndex > 0 { + n.debug("Ignoring forwarded peer removal proposal, membership changing") + n.RUnlock() return } + prop := n.prop + n.RUnlock() // Need to copy since this is underlying client/route buffer. peer := copyBytes(msg) @@ -2635,7 +2868,7 @@ func (n *raft) handleForwardedProposal(sub *subscription, c *client, _ *Account, n.RLock() // Check state under lock, we might not be leader anymore. - if n.State() != Leader { + if n.State() != Leader || !n.leaderState.Load() { n.debug("Ignoring forwarded proposal, not leader") n.RUnlock() return @@ -2697,14 +2930,18 @@ func (n *raft) sendMembershipChange(e *Entry) bool { n.Lock() defer n.Unlock() - // Only makes sense to call this with entries that change membership - if !e.ChangesMembership() { + // Only makes sense to call this with entries that change membership. + // Also, ignore if we're already changing membership. + if !e.ChangesMembership() || n.membChangeIndex > 0 { return false } + // Set to the index where we will store the membership change. + // It needs to be before we send, since if we're cluster size 1 we try to commit immediately. + n.membChangeIndex = n.pindex + 1 err := n.sendAppendEntryLocked([]*Entry{e}, true) if err != nil { - n.membChanging = false + n.membChangeIndex = 0 return false } @@ -2722,24 +2959,6 @@ func (n *raft) sendMembershipChange(e *Entry) bool { return true } -// logContainsUncommittedMembershipChange returns true if the -// log contains uncommitted entries that change membership. -// Lock should be held. -func (n *raft) logContainsUncommittedMembershipChange() (bool, error) { - for i := n.commit + 1; i <= n.pindex; i++ { - ae, err := n.loadEntry(i) - if err != nil { - return false, err - } - if len(ae.entries) > 0 && ae.entries[0].ChangesMembership() { - ae.returnToPool() - return true, nil - } - ae.returnToPool() - } - return false, nil -} - func (n *raft) runAsLeader() { if n.State() == Closed { return @@ -2748,22 +2967,6 @@ func (n *raft) runAsLeader() { n.Lock() psubj, rpsubj := n.psubj, n.rpsubj - // Check if there are any uncommitted membership changes. - // If so, we need to make sure we don't propose any new - // ones until those are committed. - found, err := n.logContainsUncommittedMembershipChange() - if err != nil { - n.warn("Error while looking for membership changes in WAL: %v", err) - n.stepdownLocked(noLeader) - n.Unlock() - return - - } - if found { - n.membChanging = true - n.debug("Log contains uncommitted membership change") - } - // For forwarded proposals, both normal and remove peer proposals. fsub, err := n.subscribe(psubj, n.handleForwardedProposal) if err != nil { @@ -3138,9 +3341,8 @@ func (n *raft) catchupFollower(ar *appendEntryResponse) { indexUpdates := newIPQueue[uint64](n.s, fmt.Sprintf("[ACC:%s] RAFT '%s' indexUpdates", n.accName, n.group)) indexUpdates.push(ae.pindex) n.progress[ar.peer] = indexUpdates - n.Unlock() - n.wg.Add(1) + n.Unlock() n.s.startGoRoutine(func() { defer n.wg.Done() n.runCatchup(ar, indexUpdates) @@ -3245,7 +3447,7 @@ func (n *raft) applyCommit(index uint64) error { committed = append(committed, e) // We are done with this membership change - n.membChanging = false + n.membChangeIndex = 0 case EntryRemovePeer: peer := string(e.Data) @@ -3260,7 +3462,7 @@ func (n *raft) applyCommit(index uint64) error { committed = append(committed, e) // We are done with this membership change - n.membChanging = false + n.membChangeIndex = 0 // If this is us and we are the leader signal the caller // to attempt to stepdown. @@ -3319,8 +3521,15 @@ func (n *raft) trackResponse(ar *appendEntryResponse) bool { indexUpdateQ.push(ar.index) } - // Ignore items already committed. - if ar.index <= n.commit { + // Ignore items already committed, or skip if this is not about an entry that matches our current term. + if ar.index <= n.commit || ar.term != n.term { + assert.AlwaysOrUnreachable(ar.term <= n.term, "Raft response term mismatch", map[string]any{ + "n.accName": n.accName, + "n.group": n.group, + "n.id": n.id, + "n.term": n.term, + "ar.term": ar.term, + }) return false } @@ -3502,9 +3711,8 @@ func (n *raft) cancelCatchup() { if n.catchup != nil && n.catchup.sub != nil { n.unsubscribe(n.catchup.sub) - // Send nil entry to signal the upper layers we are done catching up. - n.apply.push(nil) } + n.cancelCatchupSignal() n.catchup = nil } @@ -3531,9 +3739,6 @@ func (n *raft) createCatchup(ae *appendEntry) string { // Cleanup any old ones. if n.catchup != nil && n.catchup.sub != nil { n.unsubscribe(n.catchup.sub) - } else { - // Signal to the upper layer that the following entries are catchup entries, up until the nil guard. - n.apply.push(newCommittedEntry(0, []*Entry{{EntryCatchup, nil}})) } // Snapshot term and index. n.catchup = &catchupState{ @@ -3546,10 +3751,28 @@ func (n *raft) createCatchup(ae *appendEntry) string { inbox := n.newCatchupInbox() sub, _ := n.subscribe(inbox, n.handleAppendEntry) n.catchup.sub = sub - return inbox } +// Lock should be held. +func (n *raft) sendCatchupSignal() { + if n.catchup == nil || n.catchup.signal { + return + } + n.catchup.signal = true + // Signal to the upper layer that the following entries are catchup entries, up until the nil guard. + n.apply.push(newCommittedEntry(0, []*Entry{{EntryCatchup, nil}})) +} + +// Lock should be held. +func (n *raft) cancelCatchupSignal() { + if n.catchup == nil || !n.catchup.signal { + return + } + // Send nil entry to signal the upper layers we are done catching up. + n.apply.push(nil) +} + // Truncate our WAL and reset. // Lock should be held. func (n *raft) truncateWAL(term, index uint64) { @@ -3591,9 +3814,6 @@ func (n *raft) truncateWAL(term, index uint64) { if n.applied > n.processed { n.applied = n.processed } - if n.papplied > n.applied { - n.papplied = n.applied - } // Refresh bytes count after truncate. var state StreamState n.wal.FastState(&state) @@ -3607,6 +3827,11 @@ func (n *raft) truncateWAL(term, index uint64) { } // Set after we know we have truncated properly. n.pterm, n.pindex = term, index + + // Check if we're truncating an uncommitted membership change. + if n.membChangeIndex > 0 && n.membChangeIndex > index { + n.membChangeIndex = 0 + } } // Reset our WAL. This is equivalent to truncating all data from the log. @@ -3633,14 +3858,17 @@ func (n *raft) updateLeader(newLeader string) { } } } - // Reset last seen timestamps. + // Reset last seen timestamps and indices. // If we are (or were) the leader we track(ed) everyone, and don't reset. // But if we're a follower we only track the leader, and reset all others. if newLeader != n.id && !wasLeader { for peer, ps := range n.peers { + // Always reset last replicated index. + ps.li = 0 if peer == newLeader { continue } + // Only reset the last seen timestamp if this peer is not the leader. ps.ts = time.Time{} } } @@ -3900,6 +4128,7 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { } // Inherit state from appendEntry with the leader's snapshot. + hadPreviousSnapshot := n.snapfile != _EMPTY_ n.pindex = ae.pindex n.pterm = ae.pterm n.commit = ae.pindex @@ -3918,8 +4147,18 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { } n.resetInitializing() + if !hadPreviousSnapshot { + // If the first snapshot we install is received from another server, then we immediately signal + // to the upper-layer it can coalesce catchup entries. + n.sendCatchupSignal() + } // Now send snapshot to upper levels. Only send the snapshot, not the peerstate entry. n.apply.push(newCommittedEntry(n.commit, ae.entries[:1])) + if hadPreviousSnapshot { + // Signal catchup only after we've sent the snapshot. That ensures the upper-layer processes the snapshot + // as-is and can only coalesce other catchup entries after this one. + n.sendCatchupSignal() + } n.Unlock() return } @@ -3975,6 +4214,9 @@ CONTINUE: } } case EntryAddPeer: + // When receiving or restoring, mark membership as changing. + // Set to the index where this entry was stored (pindex is now this entry's index) + n.membChangeIndex = n.pindex if newPeer := string(e.Data); len(newPeer) == idLen { // Track directly, but wait for commit to be official if _, ok := n.peers[newPeer]; !ok { @@ -3983,6 +4225,10 @@ CONTINUE: // Store our peer in our global peer map for all peers. peers.LoadOrStore(newPeer, newPeer) } + case EntryRemovePeer: + // When receiving or restoring, mark membership as changing. + // Set to the index where this entry was stored (pindex is now this entry's index) + n.membChangeIndex = n.pindex } } @@ -3992,6 +4238,10 @@ CONTINUE: // Apply anything we need here. if aeCommit > n.commit { + // If we're catching up, we might need to signal that it's okay to potentially coalesce entries from here. + if catchingUp { + n.sendCatchupSignal() + } if n.paused { n.hcommit = aeCommit n.debug("Paused, not applying %d", aeCommit) @@ -4063,7 +4313,8 @@ func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) { if ar.success { // The remote node successfully committed the append entry. // They agree with our leadership and are happy with the state of the log. - // In this case ar.term doesn't matter. + // In this case ar.term was populated with the remote's pterm. If this matches + // our term, we can use it to check for quorum and up our commit. var err error var committed bool @@ -4746,7 +4997,6 @@ func (n *raft) switchToFollowerLocked(leader string) { n.leaderState.Store(false) n.leaderSince.Store(nil) n.lxfer = false - n.membChanging = false // Reset acks, we can't assume acks from a previous term are still valid in another term. if len(n.acks) > 0 { @@ -4782,6 +5032,7 @@ func (n *raft) switchToCandidate() { } // Increment the term. n.term++ + n.vote = noVote // Clear current Leader. n.updateLeader(noLeader) n.switchState(Candidate) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/reload.go b/vendor/github.com/nats-io/nats-server/v2/server/reload.go index 7afe29b80f1..c2d467af659 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/reload.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/reload.go @@ -564,21 +564,20 @@ type maxConnOption struct { // below the limit if necessary. func (m *maxConnOption) Apply(server *Server) { server.mu.Lock() - var ( - clients = make([]*client, len(server.clients)) - i = 0 - ) + clients := make([]*client, 0, len(server.clients)) // Map iteration is random, which allows us to close random connections. for _, client := range server.clients { - clients[i] = client - i++ + if isInternalClient(client.kind) { + continue + } + clients = append(clients, client) } server.mu.Unlock() - if m.newValue > 0 && len(clients) > m.newValue { + if newc := max(0, m.newValue); len(clients) > newc { // Close connections til we are within the limit. var ( - numClose = len(clients) - m.newValue + numClose = len(clients) - newc closed = 0 ) for _, client := range clients { @@ -1659,7 +1658,7 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) { return nil, fmt.Errorf("config reload not supported for jetstream max memory and store") } } - case "jetstreammetacompact", "jetstreammetacompactsize": + case "jetstreammetacompact", "jetstreammetacompactsize", "jetstreammetacompactsync": // Allowed at runtime but monitorCluster looks at s.opts directly, so no further work needed here. case "websocket": // Similar to gateways diff --git a/vendor/github.com/nats-io/nats-server/v2/server/route.go b/vendor/github.com/nats-io/nats-server/v2/server/route.go index b5850ecd354..a0384ad35c8 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/route.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/route.go @@ -88,7 +88,7 @@ type route struct { // an implicit route and sending to the remote. gossipMode byte // This will be set in case of pooling so that a route can trigger - // the creation of the next after receiving the first PONG, ensuring + // the creation of the next after receiving a PONG, ensuring // that authentication did not fail. startNewRoute *routeInfo } @@ -181,8 +181,7 @@ func (c *client) processAccountUnsub(arg []byte) { // we have an origin cluster and we force header semantics. func (c *client) processRoutedOriginClusterMsgArgs(arg []byte) error { // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_HMSG_ARGS + 1][]byte{} - args := a[:0] + args := c.argsa[:0] start := -1 for i, b := range arg { switch b { @@ -280,8 +279,7 @@ func (c *client) processRoutedOriginClusterMsgArgs(arg []byte) error { // Process an inbound HMSG specification from the remote route. func (c *client) processRoutedHeaderMsgArgs(arg []byte) error { // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_HMSG_ARGS][]byte{} - args := a[:0] + args := c.argsa[:0] var an []byte if c.kind == ROUTER { if an = c.route.accName; len(an) > 0 { @@ -377,8 +375,7 @@ func (c *client) processRoutedHeaderMsgArgs(arg []byte) error { // Process an inbound RMSG or LMSG specification from the remote route. func (c *client) processRoutedMsgArgs(arg []byte) error { // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_RMSG_ARGS][]byte{} - args := a[:0] + args := c.argsa[:0] var an []byte if c.kind == ROUTER { if an = c.route.accName; len(an) > 0 { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/server.go b/vendor/github.com/nats-io/nats-server/v2/server/server.go index d8c8a1cb2c8..aa28534dc10 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/server.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/server.go @@ -3377,7 +3377,7 @@ func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client { // If there is a max connections specified, check that adding // this new client would not push us over the max - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { + if opts.MaxConn < 0 || (opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn) { s.mu.Unlock() c.maxConnExceeded() return nil @@ -3452,7 +3452,7 @@ func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client { pre = pre[:n] } conn = &tlsMixConn{conn, bytes.NewBuffer(pre)} - addr, err := readProxyProtoHeader(conn) + addr, proxyPre, err := readProxyProtoHeader(conn) if err != nil && err != errProxyProtoUnrecognized { // err != errProxyProtoUnrecognized implies that we detected a proxy // protocol header but we failed to parse it, so don't continue. @@ -3480,7 +3480,7 @@ func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client { // that it's a non-proxied connection and we want the pre-read to remain // for the next step. if err == nil { - pre = nil + pre = proxyPre } // Because we have ProxyProtocol enabled, our earlier INFO message didn't // include the client_ip. If we need to send it again then we will include diff --git a/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go b/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go index 62a6c00e87b..938d327e04d 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go @@ -42,7 +42,6 @@ type winServiceWrapper struct { } var dockerized = false -var startupDelay = 10 * time.Second func init() { if v, exists := os.LookupEnv("NATS_DOCKERIZED"); exists && v == "1" { @@ -67,6 +66,7 @@ func (w *winServiceWrapper) Execute(args []string, changes <-chan svc.ChangeRequ status <- svc.Status{State: svc.StartPending} go w.server.Start() + var startupDelay = 10 * time.Second if v, exists := os.LookupEnv("NATS_STARTUP_DELAY"); exists { if delay, err := time.ParseDuration(v); err == nil { startupDelay = delay @@ -86,24 +86,32 @@ func (w *winServiceWrapper) Execute(args []string, changes <-chan svc.ChangeRequ } loop: - for change := range changes { - switch change.Cmd { - case svc.Interrogate: - status <- change.CurrentStatus - case svc.Stop, svc.Shutdown: - w.server.Shutdown() - break loop - case reopenLogCmd: - // File log re-open for rotating file logs. - w.server.ReOpenLogFile() - case ldmCmd: - go w.server.lameDuckMode() - case svc.ParamChange: - if err := w.server.Reload(); err != nil { - w.server.Errorf("Failed to reload server configuration: %s", err) + for { + select { + case change, ok := <-changes: + if !ok { + break loop + } + switch change.Cmd { + case svc.Interrogate: + status <- change.CurrentStatus + case svc.Stop, svc.Shutdown: + w.server.Shutdown() + break loop + case reopenLogCmd: + // File log re-open for rotating file logs. + w.server.ReOpenLogFile() + case ldmCmd: + go w.server.lameDuckMode() + case svc.ParamChange: + if err := w.server.Reload(); err != nil { + w.server.Errorf("Failed to reload server configuration: %s", err) + } + default: + w.server.Debugf("Unexpected control request: %v", change.Cmd) } - default: - w.server.Debugf("Unexpected control request: %v", change.Cmd) + case <-w.server.quitCh: + break loop } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/store.go b/vendor/github.com/nats-io/nats-server/v2/server/store.go index 15700d0736f..35b46967069 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/store.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/store.go @@ -65,6 +65,8 @@ var ( ErrCorruptStreamState = errors.New("stream state snapshot is corrupt") // ErrTooManyResults ErrTooManyResults = errors.New("too many matching results for request") + // ErrStoreOldUpdate is returned when a consumer update is older than the current state. + ErrStoreOldUpdate = errors.New("old update ignored") ) // StoreMsg is the stored message format for messages that are retained by the Store layer. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stream.go b/vendor/github.com/nats-io/nats-server/v2/server/stream.go index 5ac0483e2f7..43df7fc564c 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stream.go @@ -428,7 +428,8 @@ type stream struct { cisrun atomic.Bool // Indicates one checkInterestState is already running. // Mirror - mirror *sourceInfo + mirror *sourceInfo + mirrorConsumerSetup *time.Timer // Sources sources map[string]*sourceInfo @@ -619,19 +620,24 @@ const StreamMaxReplicas = 5 // AddStream adds a stream for the given account. func (a *Account) addStream(config *StreamConfig) (*stream, error) { - return a.addStreamWithAssignment(config, nil, nil, false) + return a.addStreamWithAssignment(config, nil, nil, false, false) +} + +// recoverStream recovers a stream from disk for the given account. +func (a *Account) recoverStream(config *StreamConfig) (*stream, error) { + return a.addStreamWithAssignment(config, nil, nil, false, true) } // AddStreamWithStore adds a stream for the given account with custome store config options. func (a *Account) addStreamWithStore(config *StreamConfig, fsConfig *FileStoreConfig) (*stream, error) { - return a.addStreamWithAssignment(config, fsConfig, nil, false) + return a.addStreamWithAssignment(config, fsConfig, nil, false, false) } func (a *Account) addStreamPedantic(config *StreamConfig, pedantic bool) (*stream, error) { - return a.addStreamWithAssignment(config, nil, nil, pedantic) + return a.addStreamWithAssignment(config, nil, nil, pedantic, false) } -func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileStoreConfig, sa *streamAssignment, pedantic bool) (*stream, error) { +func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileStoreConfig, sa *streamAssignment, pedantic, recovering bool) (*stream, error) { s, jsa, err := a.checkForJetStream() if err != nil { return nil, err @@ -678,6 +684,7 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt }() } + // Note that isClustered will be false during recovery, even if we're part of a cluster. It shouldn't be used then. js, isClustered := jsa.jetStreamAndClustered() jsa.mu.Lock() if mset, ok := jsa.streams[cfg.Name]; ok { @@ -707,25 +714,30 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt jsa.usageMu.RLock() selected, tier, hasTier := jsa.selectLimits(cfg.Replicas) jsa.usageMu.RUnlock() - reserved := int64(0) - if !isClustered { - reserved = jsa.tieredReservation(tier, cfg) - } - jsa.mu.Unlock() if !hasTier { + jsa.mu.Unlock() return nil, NewJSNoLimitsError() } - js.mu.RLock() - if isClustered { - _, reserved = tieredStreamAndReservationCount(js.cluster.streams[a.Name], tier, cfg) - } - if err := js.checkAllLimits(&selected, cfg, reserved, 0); err != nil { + + // Skip if we're recovering. + if !recovering { + reserved := int64(0) + if !isClustered { + reserved = jsa.tieredReservation(tier, cfg) + } + jsa.mu.Unlock() + js.mu.RLock() + if isClustered { + _, reserved = js.tieredStreamAndReservationCount(a.Name, tier, cfg) + } + if err := js.checkAllLimits(&selected, cfg, reserved, 0); err != nil { + js.mu.RUnlock() + return nil, err + } js.mu.RUnlock() - return nil, err + jsa.mu.Lock() } - js.mu.RUnlock() - jsa.mu.Lock() // Check for template ownership if present. if cfg.Template != _EMPTY_ && jsa.account != nil { if !jsa.checkTemplateOwnership(cfg.Template, cfg.Name) { @@ -790,11 +802,6 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt return nil, NewJSStreamSubjectOverlapError() } - if !hasTier { - jsa.mu.Unlock() - return nil, fmt.Errorf("no applicable tier found") - } - // Setup the internal clients. c := s.createInternalJetStreamClient() ic := s.createInternalJetStreamClient() @@ -1088,8 +1095,12 @@ func (mset *stream) monitorQuitC() <-chan struct{} { if mset == nil { return nil } - mset.mu.RLock() - defer mset.mu.RUnlock() + mset.mu.Lock() + defer mset.mu.Unlock() + // Recreate if a prior monitor routine was stopped. + if mset.mqch == nil { + mset.mqch = make(chan struct{}) + } return mset.mqch } @@ -1819,7 +1830,7 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo } } - // check for duplicates + // check sources for duplicates var iNames = make(map[string]struct{}) for _, src := range cfg.Sources { if src == nil || !isValidName(src.Name) { @@ -1830,6 +1841,30 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo } else { return StreamConfig{}, NewJSSourceDuplicateDetectedError() } + + if src.FilterSubject != _EMPTY_ && len(src.SubjectTransforms) != 0 { + return StreamConfig{}, NewJSSourceMultipleFiltersNotAllowedError() + } + + for _, tr := range src.SubjectTransforms { + if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { + return StreamConfig{}, NewJSSourceInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) + } + err := ValidateMapping(tr.Source, tr.Destination) + if err != nil { + return StreamConfig{}, NewJSSourceInvalidTransformDestinationError(err) + } + } + + // Check subject filters overlap. + for outer, tr := range src.SubjectTransforms { + for inner, innertr := range src.SubjectTransforms { + if inner != outer && subjectIsSubsetMatch(tr.Source, innertr.Source) { + return StreamConfig{}, NewJSSourceOverlappingSubjectFiltersError() + } + } + } + // Do not perform checks if External is provided, as it could lead to // checking against itself (if sourced stream name is the same on different JetStream) if src.External == nil { @@ -1842,30 +1877,6 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo return StreamConfig{}, NewJSSourceMaxMessageSizeTooBigError() } } - - if src.FilterSubject != _EMPTY_ && len(src.SubjectTransforms) != 0 { - return StreamConfig{}, NewJSSourceMultipleFiltersNotAllowedError() - } - - for _, tr := range src.SubjectTransforms { - if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { - return StreamConfig{}, NewJSSourceInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) - } - - err := ValidateMapping(tr.Source, tr.Destination) - if err != nil { - return StreamConfig{}, NewJSSourceInvalidTransformDestinationError(err) - } - } - - // Check subject filters overlap. - for outer, tr := range src.SubjectTransforms { - for inner, innertr := range src.SubjectTransforms { - if inner != outer && subjectIsSubsetMatch(tr.Source, innertr.Source) { - return StreamConfig{}, NewJSSourceOverlappingSubjectFiltersError() - } - } - } continue } else { if src.External.DeliverPrefix != _EMPTY_ { @@ -1957,7 +1968,7 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo // Check for literal duplication of subject interest in config // and no overlap with any JS or SYS API subject space. dset := make(map[string]struct{}, len(cfg.Subjects)) - for _, subj := range cfg.Subjects { + for i, subj := range cfg.Subjects { // Make sure the subject is valid. Check this first. if !IsValidSubject(subj) { return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("invalid subject")) @@ -1991,6 +2002,13 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo } } } + // Now check if we have multiple subjects that we do not overlap ourselves + // which would cause duplicate entries (assuming no MsgID). + for _, tsubj := range cfg.Subjects[i+1:] { + if SubjectsCollide(tsubj, subj) { + return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject %q overlaps with %q", subj, tsubj)) + } + } // Mark for duplicate check. dset[subj] = struct{}{} } @@ -2008,18 +2026,6 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo return StreamConfig{}, NewJSStreamMaxStreamBytesExceededError() } - // Now check if we have multiple subjects they we do not overlap ourselves - // which would cause duplicate entries (assuming no MsgID). - if len(cfg.Subjects) > 1 { - for _, subj := range cfg.Subjects { - for _, tsubj := range cfg.Subjects { - if tsubj != subj && SubjectsCollide(tsubj, subj) { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject %q overlaps with %q", subj, tsubj)) - } - } - } - } - // Check the subject transform if any if cfg.SubjectTransform != nil { if cfg.SubjectTransform.Source != _EMPTY_ && !IsValidSubject(cfg.SubjectTransform.Source) { @@ -2110,10 +2116,6 @@ func (jsa *jsAccount) configUpdateCheck(old, new *StreamConfig, s *Server, pedan if cfg.Name != old.Name { return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration name must match original")) } - // Can't change MaxConsumers for now. - if cfg.MaxConsumers != old.MaxConsumers { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change MaxConsumers")) - } // Can't change storage types. if cfg.Storage != old.Storage { return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change storage type")) @@ -2230,13 +2232,15 @@ func (jsa *jsAccount) configUpdateCheck(old, new *StreamConfig, s *Server, pedan js.mu.RLock() defer js.mu.RUnlock() if isClustered { - _, reserved = tieredStreamAndReservationCount(js.cluster.streams[acc.Name], tier, &cfg) + _, reserved = js.tieredStreamAndReservationCount(acc.Name, tier, &cfg) } // reservation does not account for this stream, hence add the old value - if tier == _EMPTY_ && old.Replicas > 1 { - reserved += old.MaxBytes * int64(old.Replicas) - } else { - reserved += old.MaxBytes + if old.MaxBytes > 0 { + if tier == _EMPTY_ && old.Replicas > 1 { + reserved = addSaturate(reserved, mulSaturate(int64(old.Replicas), old.MaxBytes)) + } else { + reserved = addSaturate(reserved, old.MaxBytes) + } } if err := js.checkAllLimits(&selected, &cfg, reserved, maxBytesOffset); err != nil { return nil, err @@ -2805,6 +2809,12 @@ func (mset *stream) processMirrorMsgs(mirror *sourceInfo, ready *sync.WaitGroup) // Grab stream quit channel. mset.mu.Lock() msgs, qch, siqch := mirror.msgs, mset.qch, mirror.qch + // If the mirror was already canceled before we got here, exit early. + if siqch == nil { + mset.mu.Unlock() + ready.Done() + return + } // Set the last seen as now so that we don't fail at the first check. mirror.last.Store(time.Now().UnixNano()) mset.mu.Unlock() @@ -3119,7 +3129,8 @@ func (mset *stream) scheduleSetupMirrorConsumerRetry() { // Add some jitter. next += time.Duration(rand.Intn(int(100*time.Millisecond))) + 100*time.Millisecond - time.AfterFunc(next, func() { + stopAndClearTimer(&mset.mirrorConsumerSetup) + mset.mirrorConsumerSetup = time.AfterFunc(next, func() { mset.mu.Lock() mset.setupMirrorConsumer() mset.mu.Unlock() @@ -3409,6 +3420,7 @@ func (mset *stream) setupMirrorConsumer() error { "consumer": mirror.cname, }, ) { + mirror.wg.Done() ready.Done() } } @@ -3971,7 +3983,6 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { } else { err = mset.processJetStreamMsg(m.subj, _EMPTY_, hdr, msg, 0, 0, nil, true, true) } - if err != nil { s := mset.srv if strings.Contains(err.Error(), "no space left") { @@ -3981,31 +3992,35 @@ func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { mset.mu.RLock() accName, sname, iName := mset.acc.Name, mset.cfg.Name, si.iname mset.mu.RUnlock() - - // Can happen temporarily all the time during normal operations when the sourcing stream - // is working queue/interest with a limit and discard new. - // TODO - Improve sourcing to WQ with limit and new to use flow control rather than re-creating the consumer. - if errors.Is(err, ErrMaxMsgs) || errors.Is(err, ErrMaxBytes) { + // Can happen temporarily all the time during normal operations when the sourcing stream is discard new + // (example use case is for sourcing into a work queue) + // TODO - Maybe improve sourcing to WQ with limit and new to use flow control rather than re-creating the consumer. + if errors.Is(err, ErrMaxMsgs) || errors.Is(err, ErrMaxBytes) || errors.Is(err, ErrMaxMsgsPerSubject) { // Do not need to do a full retry that includes finding the last sequence in the stream // for that source. Just re-create starting with the seq we couldn't store instead. mset.mu.Lock() mset.retrySourceConsumerAtSeq(iName, si.sseq) mset.mu.Unlock() } else { - // Log some warning for errors other than errLastSeqMismatch or errMaxMsgs. - if !errors.Is(err, errLastSeqMismatch) { + // Log some warning for errors other than errLastSeqMismatch. + if !errors.Is(err, errLastSeqMismatch) && !errors.Is(err, errMsgIdDuplicate) { s.RateLimitWarnf("Error processing inbound source %q for '%s' > '%s': %v", iName, accName, sname, err) } - // Retry in all type of errors if we are still leader. + // Retry in all type of errors we do not want to skip if we are still leader. if mset.isLeader() { - // This will make sure the source is still in mset.sources map, - // find the last sequence and then call setupSourceConsumer. - iNameMap := map[string]struct{}{iName: {}} - mset.setStartingSequenceForSources(iNameMap) - mset.mu.Lock() - mset.retrySourceConsumerAtSeq(iName, si.sseq+1) - mset.mu.Unlock() + if !errors.Is(err, errMsgIdDuplicate) { + // This will make sure the source is still in mset.sources map, + // find the last sequence and then call setupSourceConsumer. + iNameMap := map[string]struct{}{iName: {}} + mset.setStartingSequenceForSources(iNameMap) + mset.mu.Lock() + mset.retrySourceConsumerAtSeq(iName, si.sseq+1) + mset.mu.Unlock() + } else { + // skipping the message but keep processing the rest of the batch + return true + } } } } @@ -5400,6 +5415,7 @@ func (mset *stream) getDirectRequest(req *JSApiMsgGetRequest, reply string) { // processInboundJetStreamMsg handles processing messages bound for a stream. func (mset *stream) processInboundJetStreamMsg(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. + hdr = removeHeaderStatusIfPresent(hdr) if mt, traceOnly := c.isMsgTraceEnabled(); mt != nil { // If message is delivered, we need to disable the message trace headers // to prevent a trace event to be generated when a stored message @@ -5795,7 +5811,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte, mset.ddMu.Unlock() if seq > 0 { if canRespond { - response := append(pubAck, strconv.FormatUint(dde.seq, 10)...) + response := append(pubAck, strconv.FormatUint(seq, 10)...) response = append(response, ",\"duplicate\": true}"...) outq.sendMsg(reply, response) } @@ -6753,36 +6769,31 @@ type jsPubMsg struct { o *consumer } -var jsPubMsgPool sync.Pool +var jsPubMsgPool = sync.Pool{ + New: func() any { + return &jsPubMsg{} + }, +} func newJSPubMsg(dsubj, subj, reply string, hdr, msg []byte, o *consumer, seq uint64) *jsPubMsg { - var m *jsPubMsg - var buf []byte - pm := jsPubMsgPool.Get() - if pm != nil { - m = pm.(*jsPubMsg) - buf = m.buf[:0] - if hdr != nil { - hdr = append(m.hdr[:0], hdr...) - } - } else { - m = new(jsPubMsg) - } + m := getJSPubMsgFromPool() + if m.buf == nil { + m.buf = make([]byte, 0, len(hdr)+len(msg)) + } + buf := append(m.buf[:0], hdr...) + buf = append(buf, msg...) + hdr = buf[:len(hdr):len(hdr)] + msg = buf[len(hdr):] // When getting something from a pool it is critical that all fields are // initialized. Doing this way guarantees that if someone adds a field to // the structure, the compiler will fail the build if this line is not updated. (*m) = jsPubMsg{dsubj, reply, StoreMsg{subj, hdr, msg, buf, seq, 0}, o} - return m } // Gets a jsPubMsg from the pool. func getJSPubMsgFromPool() *jsPubMsg { - pm := jsPubMsgPool.Get() - if pm != nil { - return pm.(*jsPubMsg) - } - return new(jsPubMsg) + return jsPubMsgPool.Get().(*jsPubMsg) } func (pm *jsPubMsg) returnToPool() { @@ -6793,9 +6804,6 @@ func (pm *jsPubMsg) returnToPool() { if len(pm.buf) > 0 { pm.buf = pm.buf[:0] } - if len(pm.hdr) > 0 { - pm.hdr = pm.hdr[:0] - } jsPubMsgPool.Put(pm) } @@ -7691,15 +7699,8 @@ func (mset *stream) ackMsg(o *consumer, seq uint64) bool { return false } - var shouldRemove bool - switch mset.cfg.Retention { - case WorkQueuePolicy: - // Normally we just remove a message when its ack'd here but if we have direct consumers - // from sources and/or mirrors we need to make sure they have delivered the msg. - shouldRemove = mset.directs <= 0 || mset.noInterest(seq, o) - case InterestPolicy: - shouldRemove = mset.noInterest(seq, o) - } + // If there's no interest left on this message for all consumers, we can remove it. + shouldRemove := mset.noInterest(seq, nil) // If nothing else to do. if !shouldRemove { @@ -7810,7 +7811,7 @@ func (a *Account) RestoreStream(ncfg *StreamConfig, r io.Reader) (*stream, error if hasTier { if isClustered { js.mu.RLock() - _, reserved = tieredStreamAndReservationCount(js.cluster.streams[a.Name], tier, &cfg) + _, reserved = js.tieredStreamAndReservationCount(a.Name, tier, &cfg) js.mu.RUnlock() } else { reserved = jsa.tieredReservation(tier, &cfg) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go index d0cd08ac955..00ebdc46f20 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go @@ -124,7 +124,25 @@ func (t *SubjectTree[T]) Match(filter []byte, cb func(subject []byte, val *T)) { var raw [16][]byte parts := genParts(filter, raw[:0]) var _pre [256]byte - t.match(t.root, parts, _pre[:0], cb) + t.match(t.root, parts, _pre[:0], func(subject []byte, val *T) bool { + cb(subject, val) + return true + }) +} + +// MatchUntil will match against a subject that can have wildcards and invoke +// the callback func for each matched value. +// Returning false from the callback will stop matching immediately. +// Returns true if matching ran to completion, false if callback stopped it early. +func (t *SubjectTree[T]) MatchUntil(filter []byte, cb func(subject []byte, val *T) bool) bool { + if t == nil || t.root == nil || len(filter) == 0 || cb == nil { + return true + } + // We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'. + var raw [16][]byte + parts := genParts(filter, raw[:0]) + var _pre [256]byte + return t.match(t.root, parts, _pre[:0], cb) } // IterOrdered will walk all entries in the SubjectTree lexicographically. The callback can return false to terminate the walk. @@ -296,7 +314,8 @@ func (t *SubjectTree[T]) delete(np *node, subject []byte, si int) (*T, bool) { // Internal function which can be called recursively to match all leaf nodes to a given filter subject which // once here has been decomposed to parts. These parts only care about wildcards, both pwc and fwc. -func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T)) { +// Returns false if the callback requested to stop matching. +func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T) bool) bool { // Capture if we are sitting on a terminal fwc. var hasFWC bool if lp := len(parts); lp > 0 && len(parts[lp-1]) > 0 && parts[lp-1][0] == fwc { @@ -307,15 +326,17 @@ func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subje nparts, matched := n.matchParts(parts) // Check if we did not match. if !matched { - return + return true } // We have matched here. If we are a leaf and have exhausted all parts or he have a FWC fire callback. if n.isLeaf() { if len(nparts) == 0 || (hasFWC && len(nparts) == 1) { ln := n.(*leaf[T]) - cb(append(pre, ln.suffix...), &ln.value) + if !cb(append(pre, ln.suffix...), &ln.value) { + return false + } } - return + return true } // We have normal nodes here. // We need to append our prefix @@ -343,17 +364,23 @@ func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subje if cn.isLeaf() { ln := cn.(*leaf[T]) if len(ln.suffix) == 0 { - cb(append(pre, ln.suffix...), &ln.value) + if !cb(append(pre, ln.suffix...), &ln.value) { + return false + } } else if hasTermPWC && bytes.IndexByte(ln.suffix, tsep) < 0 { - cb(append(pre, ln.suffix...), &ln.value) + if !cb(append(pre, ln.suffix...), &ln.value) { + return false + } } } else if hasTermPWC { // We have terminal pwc so call into match again with the child node. - t.match(cn, nparts, pre, cb) + if !t.match(cn, nparts, pre, cb) { + return false + } } } // Return regardless. - return + return true } // If we are sitting on a terminal fwc, put back and continue. if hasFWC && len(nparts) == 0 { @@ -370,18 +397,21 @@ func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subje // to see if we match further down. for _, cn := range n.children() { if cn != nil { - t.match(cn, nparts, pre, cb) + if !t.match(cn, nparts, pre, cb) { + return false + } } } - return + return true } // Here we have normal traversal, so find the next child. nn := n.findChild(p) if nn == nil { - return + return true } n, parts = *nn, nparts } + return true } // Internal iter function to walk nodes in lexicographical order. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go index 150301e32fa..2589cd21297 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/sublist.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/sublist.go @@ -1327,8 +1327,9 @@ func SubjectsCollide(subj1, subj2 string) bool { if subj1 == subj2 { return true } - toks1 := strings.Split(subj1, tsep) - toks2 := strings.Split(subj2, tsep) + tsa, tsb := [32]string{}, [32]string{} + toks1 := tokenizeSubjectIntoSlice(tsa[:0], subj1) + toks2 := tokenizeSubjectIntoSlice(tsb[:0], subj2) pwc1, fwc1 := analyzeTokens(toks1) pwc2, fwc2 := analyzeTokens(toks2) // if both literal just string compare. @@ -1338,9 +1339,9 @@ func SubjectsCollide(subj1, subj2 string) bool { } // So one or both have wildcards. If one is literal than we can do subset matching. if l1 && !l2 { - return isSubsetMatch(toks1, subj2) + return isSubsetMatchTokenized(toks1, toks2) } else if l2 && !l1 { - return isSubsetMatch(toks2, subj1) + return isSubsetMatchTokenized(toks2, toks1) } // Both have wildcards. // If they only have partials then the lengths must match. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/util.go b/vendor/github.com/nats-io/nats-server/v2/server/util.go index f8ce7ab4987..ff90838bb8c 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/util.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/util.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math" + "math/bits" "net" "net/url" "reflect" @@ -165,7 +166,7 @@ func urlsAreEqual(u1, u2 *url.URL) bool { // e.g. comma(834142) -> 834,142 // // This function was copied from the github.com/dustin/go-humanize -// package and is Copyright Dustin Sallings +// package (MIT License) and is Copyright Dustin Sallings func comma(v int64) string { sign := "" @@ -363,3 +364,23 @@ func parallelTaskQueue(mp int) chan<- func() { } return tq } + +// addSaturate returns a + b, saturating at math.MaxInt64. +// Both a and b must be non-negative. +func addSaturate(a, b int64) int64 { + sum, carry := bits.Add64(uint64(a), uint64(b), 0) + if carry != 0 || sum > uint64(math.MaxInt64) { + return math.MaxInt64 + } + return int64(sum) +} + +// mulSaturate returns a * b, saturating at math.MaxInt64. +// Both a and b must be non-negative. +func mulSaturate(a, b int64) int64 { + hi, lo := bits.Mul64(uint64(a), uint64(b)) + if hi != 0 || lo > uint64(math.MaxInt64) { + return math.MaxInt64 + } + return int64(lo) +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/websocket.go b/vendor/github.com/nats-io/nats-server/v2/server/websocket.go index 7f352996426..0ee1ca7db90 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/websocket.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/websocket.go @@ -60,6 +60,8 @@ const ( wsMaxControlPayloadSize = 125 wsFrameSizeForBrowsers = 4096 // From experiment, webrowsers behave better with limited frame size wsCompressThreshold = 64 // Don't compress for small buffer(s) + wsMaxMsgPayloadMultiple = 8 + wsMaxMsgPayloadLimit = 64 * 1024 * 1024 wsCloseSatusSize = 2 // From https://tools.ietf.org/html/rfc6455#section-11.7 @@ -128,7 +130,7 @@ type srvWebsocket struct { server *http.Server listener net.Listener listenerErr error - allowedOrigins map[string]*allowedOrigin // host will be the key + allowedOrigins map[string][]*allowedOrigin // host will be the key sameOrigin bool connectURLs []string connectURLsMap refCountedUrlSet @@ -154,7 +156,7 @@ type wsUpgradeResult struct { } type wsReadInfo struct { - rem int + rem uint64 fs bool ff bool fc bool @@ -163,31 +165,57 @@ type wsReadInfo struct { mkey [4]byte cbufs [][]byte coff int + csz uint64 } func (r *wsReadInfo) init() { r.fs, r.ff = true, true } +func (r *wsReadInfo) resetCompressedState() { + r.fs = true + r.ff = true + r.fc = false + r.rem = 0 + r.cbufs = nil + r.coff = 0 + r.csz = 0 +} + +// Compressed WebSocket messages have to be accumulated before they can be +// decompressed and handed to the parser, so this transport limit needs to +// allow batching several max_payload-sized NATS operations while still +// capping resource usage on the buffered compressed path. +func wsMaxMessageSize(mpay int) uint64 { + if mpay <= 0 { + mpay = MAX_PAYLOAD_SIZE + } + limit := uint64(mpay) * wsMaxMsgPayloadMultiple + if limit > wsMaxMsgPayloadLimit { + limit = wsMaxMsgPayloadLimit + } + return limit +} + // Returns a slice containing `needed` bytes from the given buffer `buf` // starting at position `pos`, and possibly read from the given reader `r`. // When bytes are present in `buf`, the `pos` is incremented by the number // of bytes found up to `needed` and the new position is returned. If not // enough bytes are found, the bytes found in `buf` are copied to the returned // slice and the remaning bytes are read from `r`. -func wsGet(r io.Reader, buf []byte, pos, needed int) ([]byte, int, error) { - avail := len(buf) - pos +func wsGet(r io.Reader, buf []byte, pos, needed uint64) ([]byte, uint64, error) { + avail := uint64(len(buf)) - pos if avail >= needed { return buf[pos : pos+needed], pos + needed, nil } b := make([]byte, needed) - start := copy(b, buf[pos:]) + start := uint64(copy(b, buf[pos:])) for start != needed { n, err := r.Read(b[start:cap(b)]) if err != nil { return nil, 0, err } - start += n + start += uint64(n) } return b, pos + avail, nil } @@ -206,13 +234,43 @@ func (c *client) isWebsocket() bool { // // Client lock MUST NOT be held on entry. func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, error) { + var bufs [][]byte + err := c.wsReadLoop(r, ior, buf, func(b []byte, compressed, final bool) error { + if compressed { + return errors.New("compressed websocket frames require wsReadAndParse") + } + bufs = append(bufs, b) + return nil + }) + return bufs, err +} + +func (c *client) wsReadAndParse(r *wsReadInfo, ior io.Reader, buf []byte) error { + mpay := int(atomic.LoadInt32(&c.mpay)) + if mpay <= 0 { + mpay = MAX_PAYLOAD_SIZE + } + return c.wsReadLoop(r, ior, buf, func(b []byte, compressed, final bool) error { + if compressed { + if err := c.wsDecompressAndParse(r, b, final, mpay); err != nil { + r.resetCompressedState() + return err + } + if final { + r.fc = false + } + return nil + } + return c.parse(b) + }) +} + +func (c *client) wsReadLoop(r *wsReadInfo, ior io.Reader, buf []byte, handle func([]byte, bool, bool) error) error { var ( - bufs [][]byte tmpBuf []byte err error - pos int - max = len(buf) - mpay = int(atomic.LoadInt32(&c.mpay)) + pos uint64 + max = uint64(len(buf)) ) for pos != max { if r.fs { @@ -220,69 +278,80 @@ func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, err frameType := wsOpCode(b0 & 0xF) final := b0&wsFinalBit != 0 compressed := b0&wsRsv1Bit != 0 + if b0&(wsRsv2Bit|wsRsv3Bit) != 0 { + return c.wsHandleProtocolError("RSV2 and RSV3 must be clear") + } + if compressed && !c.ws.compress { + return c.wsHandleProtocolError("compressed frame received without negotiated permessage-deflate") + } pos++ tmpBuf, pos, err = wsGet(ior, buf, pos, 1) if err != nil { - return bufs, err + return err } b1 := tmpBuf[0] // Clients MUST set the mask bit. If not set, reject. // However, LEAF by default will not have masking, unless they are forced to, by configuration. if r.mask && b1&wsMaskBit == 0 { - return bufs, c.wsHandleProtocolError("mask bit missing") + return c.wsHandleProtocolError("mask bit missing") } // Store size in case it is < 125 - r.rem = int(b1 & 0x7F) + r.rem = uint64(b1 & 0x7F) switch frameType { case wsPingMessage, wsPongMessage, wsCloseMessage: if r.rem > wsMaxControlPayloadSize { - return bufs, c.wsHandleProtocolError( + return c.wsHandleProtocolError( fmt.Sprintf("control frame length bigger than maximum allowed of %v bytes", wsMaxControlPayloadSize)) } if !final { - return bufs, c.wsHandleProtocolError("control frame does not have final bit set") + return c.wsHandleProtocolError("control frame does not have final bit set") + } + if compressed { + return c.wsHandleProtocolError("control frame must not be compressed") } case wsTextMessage, wsBinaryMessage: if !r.ff { - return bufs, c.wsHandleProtocolError("new message started before final frame for previous message was received") + return c.wsHandleProtocolError("new message started before final frame for previous message was received") } r.ff = final r.fc = compressed case wsContinuationFrame: // Compressed bit must be only set in the first frame if r.ff || compressed { - return bufs, c.wsHandleProtocolError("invalid continuation frame") + return c.wsHandleProtocolError("invalid continuation frame") } r.ff = final default: - return bufs, c.wsHandleProtocolError(fmt.Sprintf("unknown opcode %v", frameType)) + return c.wsHandleProtocolError(fmt.Sprintf("unknown opcode %v", frameType)) } switch r.rem { case 126: tmpBuf, pos, err = wsGet(ior, buf, pos, 2) if err != nil { - return bufs, err + return err } - r.rem = int(binary.BigEndian.Uint16(tmpBuf)) + r.rem = uint64(binary.BigEndian.Uint16(tmpBuf)) case 127: tmpBuf, pos, err = wsGet(ior, buf, pos, 8) if err != nil { - return bufs, err + return err + } + if r.rem = binary.BigEndian.Uint64(tmpBuf); r.rem&(uint64(1)<<63) != 0 { + return c.wsHandleProtocolError("invalid 64-bit payload length") } - r.rem = int(binary.BigEndian.Uint64(tmpBuf)) } if r.mask { // Read masking key tmpBuf, pos, err = wsGet(ior, buf, pos, 4) if err != nil { - return bufs, err + return err } copy(r.mkey[:], tmpBuf) r.mkpos = 0 @@ -292,7 +361,7 @@ func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, err if wsIsControlFrame(frameType) { pos, err = c.wsHandleControlFrame(r, frameType, ior, buf, pos) if err != nil { - return bufs, err + return err } continue } @@ -301,53 +370,26 @@ func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, err r.fs = false } if pos < max { - var b []byte - var n int - - n = r.rem + n := r.rem if pos+n > max { n = max - pos } - b = buf[pos : pos+n] + b := buf[pos : pos+n] pos += n r.rem -= n // If needed, unmask the buffer if r.mask { r.unmask(b) } - addToBufs := true - // Handle compressed message - if r.fc { - // Assume that we may have continuation frames or not the full payload. - addToBufs = false - // Make a copy of the buffer before adding it to the list - // of compressed fragments. - r.cbufs = append(r.cbufs, append([]byte(nil), b...)) - // When we have the final frame and we have read the full payload, - // we can decompress it. - if r.ff && r.rem == 0 { - b, err = r.decompress(mpay) - if err != nil { - return bufs, err - } - r.fc = false - // Now we can add to `bufs` - addToBufs = true - } - } - // For non compressed frames, or when we have decompressed the - // whole message. - if addToBufs { - bufs = append(bufs, b) + if err := handle(b, r.fc, r.ff && r.rem == 0); err != nil { + return err } - // If payload has been fully read, then indicate that next - // is the start of a frame. if r.rem == 0 { r.fs = true } } } - return bufs, nil + return nil } func (r *wsReadInfo) Read(dst []byte) (int, error) { @@ -391,6 +433,9 @@ func (r *wsReadInfo) nextCBuf() []byte { } func (r *wsReadInfo) ReadByte() (byte, error) { + for len(r.cbufs) > 0 && len(r.cbufs[0]) == 0 { + r.nextCBuf() + } if len(r.cbufs) == 0 { return 0, io.EOF } @@ -400,49 +445,71 @@ func (r *wsReadInfo) ReadByte() (byte, error) { return b, nil } -// decompress decompresses the collected buffers. -// The size of the decompressed buffer will be limited to the `mpay` value. -// If, while decompressing, the resulting uncompressed buffer exceeds this -// limit, the decompression stops and an empty buffer and the ErrMaxPayload -// error are returned. -func (r *wsReadInfo) decompress(mpay int) ([]byte, error) { - // If not limit is specified, use the default maximum payload size. - if mpay <= 0 { - mpay = MAX_PAYLOAD_SIZE +func (c *client) wsDecompressAndParse(r *wsReadInfo, b []byte, final bool, mpay int) error { + limit := wsMaxMessageSize(mpay) + if len(b) > 0 { + if r.csz+uint64(len(b)) > limit { + return ErrMaxPayload + } + r.cbufs = append(r.cbufs, append([]byte(nil), b...)) + r.csz += uint64(len(b)) + } + if !final { + return nil + } + if r.csz+uint64(len(compressLastBlock)) > limit { + return ErrMaxPayload } - r.coff = 0 - // As per https://tools.ietf.org/html/rfc7692#section-7.2.2 - // add 0x00, 0x00, 0xff, 0xff and then a final block so that flate reader - // does not report unexpected EOF. r.cbufs = append(r.cbufs, compressLastBlock) - // Get a decompressor from the pool and bind it to this object (wsReadInfo) - // that provides Read() and ReadByte() APIs that will consume the compressed - // buffers (r.cbufs). + r.csz += uint64(len(compressLastBlock)) + r.coff = 0 d, _ := decompressorPool.Get().(io.ReadCloser) if d == nil { d = flate.NewReader(r) } else { d.(flate.Resetter).Reset(r, nil) } - // Use a LimitedReader to limit the decompressed size. - // We use "limit+1" bytes for "N" so we can detect if the limit is exceeded. + defer func() { + d.Close() + decompressorPool.Put(d) + r.cbufs = nil + r.coff = 0 + r.csz = 0 + }() lr := io.LimitedReader{R: d, N: int64(mpay + 1)} - b, err := io.ReadAll(&lr) - if err == nil && len(b) > mpay { - // Decompressed data exceeds the maximum payload size. - b, err = nil, ErrMaxPayload - } - lr.R = nil - decompressorPool.Put(d) - // Now reset the compressed buffers list. - r.cbufs = nil - return b, err + buf := make([]byte, 32*1024) + total := 0 + for { + n, err := lr.Read(buf) + if n > 0 { + pn := n + if total+n > mpay { + pn = mpay - total + } + if pn > 0 { + if err := c.parse(buf[:pn]); err != nil { + return err + } + } + total += n + if total > mpay { + return ErrMaxPayload + } + } + if err == nil { + continue + } + if err == io.EOF { + return nil + } + return err + } } // Handles the PING, PONG and CLOSE websocket control frames. // // Client lock MUST NOT be held on entry. -func (c *client) wsHandleControlFrame(r *wsReadInfo, frameType wsOpCode, nc io.Reader, buf []byte, pos int) (int, error) { +func (c *client) wsHandleControlFrame(r *wsReadInfo, frameType wsOpCode, nc io.Reader, buf []byte, pos uint64) (uint64, error) { var payload []byte var err error @@ -461,6 +528,9 @@ func (c *client) wsHandleControlFrame(r *wsReadInfo, frameType wsOpCode, nc io.R status := wsCloseStatusNoStatusReceived var body string lp := len(payload) + if lp == 1 { + return pos, c.wsHandleProtocolError("close frame payload cannot be 1 byte") + } // If there is a payload, the status is represented as a 2-byte // unsigned integer (in network byte order). Then, there may be an // optional body. @@ -468,6 +538,9 @@ func (c *client) wsHandleControlFrame(r *wsReadInfo, frameType wsOpCode, nc io.R if hasStatus { // Decode the status status = int(binary.BigEndian.Uint16(payload[:wsCloseSatusSize])) + if !wsIsValidCloseStatus(status) { + return pos, c.wsHandleProtocolError(fmt.Sprintf("invalid close status code %v", status)) + } // Now if there is a body, capture it and make sure this is a valid UTF-8. if hasBody { body = string(payload[wsCloseSatusSize:]) @@ -704,6 +777,21 @@ func (c *client) wsHandleProtocolError(message string) error { return errors.New(message) } +func wsIsValidCloseStatus(code int) bool { + switch code { + case wsCloseStatusNoStatusReceived, 1004, 1006, wsCloseStatusTLSHandshake: + return false + } + if code < 1000 || code >= 5000 { + return false + } + // 1016-2999 are currently reserved. + if code >= 1016 && code <= 2999 { + return false + } + return true +} + // Create a close message with the given `status` and `body`. // If the `body` is more than the maximum allows control frame payload size, // it is truncated and "..." is added at the end (as a hint that message @@ -763,6 +851,10 @@ func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeRe if key == _EMPTY_ { return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "key missing") } + decoded, err := base64.StdEncoding.DecodeString(key) + if err != nil || len(decoded) != 16 { + return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "invalid websocket key") + } // Point 6. if !wsHeaderContains(r.Header, "Sec-Websocket-Version", "13") { return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "invalid version") @@ -784,7 +876,10 @@ func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeRe // We will do masking if asked (unless we reject for tests) noMasking := r.Header.Get(wsNoMaskingHeader) == wsNoMaskingValue && !wsTestRejectNoMasking - h := w.(http.Hijacker) + h, ok := w.(http.Hijacker) + if !ok { + return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "websocket upgrade not supported") + } conn, brw, err := h.Hijack() if err != nil { if conn != nil { @@ -832,9 +927,11 @@ func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeRe // Check for X-Forwarded-For header if cips, ok := r.Header[wsXForwardedForHeader]; ok { - cip := cips[0] - if net.ParseIP(cip) != nil { - ws.clientIP = cip + if len(cips) > 0 { + cip := cips[0] + if net.ParseIP(cip) != nil { + ws.clientIP = cip + } } } @@ -964,7 +1061,11 @@ func (w *srvWebsocket) checkOrigin(r *http.Request) error { if err != nil { return err } - if oh != rh || op != rp { + rs := "http" + if r.TLS != nil { + rs = "https" + } + if oh != rh || op != rp || !strings.EqualFold(u.Scheme, rs) { return errors.New("not same origin") } // I guess it is possible to have cases where one wants to check @@ -973,9 +1074,16 @@ func (w *srvWebsocket) checkOrigin(r *http.Request) error { } if !listEmpty { w.mu.RLock() - ao := w.allowedOrigins[oh] + origins := w.allowedOrigins[oh] w.mu.RUnlock() - if ao == nil || u.Scheme != ao.scheme || op != ao.port { + var allowed bool + for _, ao := range origins { + if u.Scheme == ao.scheme && op == ao.port { + allowed = true + break + } + } + if !allowed { return errors.New("not in the allowed list") } } @@ -1029,7 +1137,17 @@ func validateWebsocketOptions(o *Options) error { } // Make sure that allowed origins, if specified, can be parsed. for _, ao := range wo.AllowedOrigins { - if _, err := url.Parse(ao); err != nil { + u, err := url.ParseRequestURI(ao) + if err != nil { + return fmt.Errorf("unable to parse allowed origin: %v", err) + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("unable to parse allowed origin %q: allowed origins must be absolute URLs with http or https scheme", ao) + } + if u.Host == _EMPTY_ { + return fmt.Errorf("unable to parse allowed origin %q: host is required", ao) + } + if _, _, err := wsGetHostAndPort(u.Scheme == "https", u.Host); err != nil { return fmt.Errorf("unable to parse allowed origin: %v", err) } } @@ -1101,9 +1219,9 @@ func (s *Server) wsSetOriginOptions(o *WebsocketOpts) { } h, p, _ := wsGetHostAndPort(u.Scheme == "https", u.Host) if ws.allowedOrigins == nil { - ws.allowedOrigins = make(map[string]*allowedOrigin, len(o.AllowedOrigins)) + ws.allowedOrigins = make(map[string][]*allowedOrigin, len(o.AllowedOrigins)) } - ws.allowedOrigins[h] = &allowedOrigin{scheme: u.Scheme, port: p} + ws.allowedOrigins[h] = append(ws.allowedOrigins[h], &allowedOrigin{scheme: u.Scheme, port: p}) } } @@ -1324,7 +1442,7 @@ func (s *Server) createWSClient(conn net.Conn, ws *websocket) *client { return c } - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { + if opts.MaxConn < 0 || (opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn) { s.mu.Unlock() c.maxConnExceeded() return nil @@ -1402,7 +1520,7 @@ func (c *client) wsCollapsePtoNB() (net.Buffers, int64) { cp.Reset(buf) } var csz int - for _, b := range nb { + for i, b := range nb { for len(b) > 0 { n, err := cp.Write(b) if err != nil { @@ -1414,7 +1532,10 @@ func (c *client) wsCollapsePtoNB() (net.Buffers, int64) { } b = b[n:] } - nbPoolPut(b) // No longer needed as contents written to compressor. + // Use original slice since capacity will change to zero + // in the loop after consuming the buffer, which will make + // nbPoolPut discard it. + nbPoolPut(nb[i]) } if err := cp.Flush(); err != nil { c.Errorf("Error during compression: %v", err) diff --git a/vendor/github.com/nats-io/nats.go/README.md b/vendor/github.com/nats-io/nats.go/README.md index 9340a8899ec..566a796eec8 100644 --- a/vendor/github.com/nats-io/nats.go/README.md +++ b/vendor/github.com/nats-io/nats.go/README.md @@ -23,7 +23,7 @@ A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io go get github.com/nats-io/nats.go@latest # To get a specific version: -go get github.com/nats-io/nats.go@v1.48.0 +go get github.com/nats-io/nats.go@v1.49.0 # Note that the latest major version for NATS Server is v2: go get github.com/nats-io/nats-server/v2@latest @@ -134,7 +134,7 @@ The simplest form is to use the helper method UserCredentials(credsFilepath). nc, err := nats.Connect(url, nats.UserCredentials("user.creds")) ``` -The helper methods creates two callback handlers to present the user JWT and sign the nonce challenge from the server. +The helper method creates two callback handlers to present the user JWT and sign the nonce challenge from the server. The core client library never has direct access to your private key and simply performs the callback for signing the server challenge. The helper will load and wipe and erase memory it uses for each connect or reconnect. @@ -177,7 +177,7 @@ nc, err := nats.Connect("tls://nats.demo.io:4443") // We provide a helper method to make this case easier. nc, err = nats.Connect("tls://localhost:4443", nats.RootCAs("./configs/certs/ca.pem")) -// If the server requires client certificate, there is an helper function for that too: +// If the server requires client certificate, there is a helper function for that too: cert := nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem") nc, err = nats.Connect("tls://localhost:4443", cert) @@ -210,17 +210,17 @@ if err != nil { // "*" matches any token, at any level of the subject. nc.Subscribe("foo.*.baz", func(m *Msg) { - fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); + fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)) }) nc.Subscribe("foo.bar.*", func(m *Msg) { - fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); + fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)) }) // ">" matches any length of the tail of a subject, and can only be the last token // E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22' nc.Subscribe("foo.>", func(m *Msg) { - fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)); + fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data)) }) // Matches all of the above @@ -237,7 +237,7 @@ nc.Publish("foo.bar.baz", []byte("Hello World")) // Normal subscribers will continue to work as expected. nc.QueueSubscribe("foo", "job_workers", func(_ *Msg) { - received += 1; + received += 1 }) ``` @@ -267,9 +267,9 @@ fmt.Println("All clear!") // FlushTimeout specifies a timeout value as well. err := nc.FlushTimeout(1*time.Second) if err != nil { - fmt.Println("All clear!") -} else { fmt.Println("Flushed timed out!") +} else { + fmt.Println("All clear!") } // Auto-unsubscribe after MAX_WANTED messages received @@ -285,7 +285,7 @@ nc1.Subscribe("foo", func(m *Msg) { fmt.Printf("Received a message: %s\n", string(m.Data)) }) -nc2.Publish("foo", []byte("Hello World!")); +nc2.Publish("foo", []byte("Hello World!")) ``` @@ -339,7 +339,7 @@ nc, err = nats.Connect("nats://localhost:4222", nats.UserInfo("foo", "bar")) // For token based authentication: nc, err = nats.Connect("nats://localhost:4222", nats.Token("S3cretT0ken")) -// You can even pass the two at the same time in case one of the server +// You can even pass the two at the same time in case one of the servers // in the mesh requires token instead of user name and password. nc, err = nats.Connect("nats://localhost:4222", nats.UserInfo("foo", "bar"), @@ -372,7 +372,7 @@ msg, err := sub.NextMsgWithContext(ctx) ``` -## Backwards compatibility +## Backward compatibility In the development of nats.go, we are committed to maintaining backward compatibility and ensuring a stable and reliable experience for all users. In general, we follow the standard go compatibility guidelines. However, it's important to clarify our stance on certain types of changes: diff --git a/vendor/github.com/nats-io/nats.go/go_test.mod b/vendor/github.com/nats-io/nats.go/go_test.mod index c3b7499e04b..6bed135a81b 100644 --- a/vendor/github.com/nats-io/nats.go/go_test.mod +++ b/vendor/github.com/nats-io/nats.go/go_test.mod @@ -4,19 +4,19 @@ go 1.24.0 require ( github.com/golang/protobuf v1.4.2 - github.com/klauspost/compress v1.18.0 + github.com/klauspost/compress v1.18.2 github.com/nats-io/jwt/v2 v2.8.0 - github.com/nats-io/nats-server/v2 v2.12.0 - github.com/nats-io/nkeys v0.4.11 + github.com/nats-io/nats-server/v2 v2.12.3 + github.com/nats-io/nkeys v0.4.12 github.com/nats-io/nuid v1.0.1 google.golang.org/protobuf v1.23.0 ) require ( - github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op // indirect - github.com/google/go-tpm v0.9.5 // indirect - github.com/minio/highwayhash v1.0.3 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/time v0.13.0 // indirect + github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op // indirect + github.com/google/go-tpm v0.9.7 // indirect + github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/time v0.14.0 // indirect ) diff --git a/vendor/github.com/nats-io/nats.go/go_test.sum b/vendor/github.com/nats-io/nats.go/go_test.sum index 9b1b8c3283d..beb0dffe89a 100644 --- a/vendor/github.com/nats-io/nats.go/go_test.sum +++ b/vendor/github.com/nats-io/nats.go/go_test.sum @@ -1,5 +1,5 @@ -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM= +github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -12,27 +12,27 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= -github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= +github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk= +github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= -github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74= -github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww= -github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= -github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= +github.com/nats-io/nats-server/v2 v2.12.3 h1:KRv+1n7lddMVgkJPQer+pt36TcO0ENxjilBmeWdjcHs= +github.com/nats-io/nats-server/v2 v2.12.3/go.mod h1:MQXjG9WjyXKz9koWzUc3jYUMKD8x3CLmTNy91IQQz3Y= +github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc= +github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/vendor/github.com/nats-io/nats.go/js.go b/vendor/github.com/nats-io/nats.go/js.go index def5941184e..c3edf4cd2f7 100644 --- a/vendor/github.com/nats-io/nats.go/js.go +++ b/vendor/github.com/nats-io/nats.go/js.go @@ -296,8 +296,7 @@ type jsOpts struct { } const ( - defaultRequestWait = 5 * time.Second - defaultAccountCheck = 20 * time.Second + defaultRequestWait = 5 * time.Second ) // JetStream returns a JetStreamContext for messaging and stream management. @@ -2835,12 +2834,15 @@ func ConsumerFilterSubjects(subjects ...string) SubOpt { func (sub *Subscription) ConsumerInfo() (*ConsumerInfo, error) { sub.mu.Lock() // TODO(dlc) - Better way to mark especially if we attach. - if sub.jsi == nil || sub.jsi.consumer == _EMPTY_ { - if sub.jsi.ordered { - sub.mu.Unlock() + if sub.jsi == nil { + sub.mu.Unlock() + return nil, ErrTypeSubscription + } else if sub.jsi.consumer == _EMPTY_ { + ordered := sub.jsi.ordered + sub.mu.Unlock() + if ordered { return nil, ErrConsumerInfoOnOrderedReset } - sub.mu.Unlock() return nil, ErrTypeSubscription } diff --git a/vendor/github.com/nats-io/nats.go/kv.go b/vendor/github.com/nats-io/nats.go/kv.go index dda644ec732..6459e7602fc 100644 --- a/vendor/github.com/nats-io/nats.go/kv.go +++ b/vendor/github.com/nats-io/nats.go/kv.go @@ -105,6 +105,9 @@ type KeyValueStatus interface { // IsCompressed indicates if the data is compressed on disk IsCompressed() bool + + // Config returns the original configuration used to create the bucket + Config() KeyValueConfig } // KeyWatcher is what is returned when doing a watch. @@ -1208,6 +1211,24 @@ func (s *KeyValueBucketStatus) Bytes() uint64 { return s.nfo.State.Bytes } // IsCompressed indicates if the data is compressed on disk func (s *KeyValueBucketStatus) IsCompressed() bool { return s.nfo.Config.Compression != NoCompression } +func (s *KeyValueBucketStatus) Config() KeyValueConfig { + return KeyValueConfig{ + Bucket: s.bucket, + Description: s.nfo.Config.Description, + MaxValueSize: s.nfo.Config.MaxMsgSize, + History: uint8(s.nfo.Config.MaxMsgsPerSubject), + TTL: s.nfo.Config.MaxAge, + MaxBytes: s.nfo.Config.MaxBytes, + Storage: s.nfo.Config.Storage, + Replicas: s.nfo.Config.Replicas, + Placement: s.nfo.Config.Placement, + RePublish: s.nfo.Config.RePublish, + Mirror: s.nfo.Config.Mirror, + Sources: s.nfo.Config.Sources, + Compression: s.nfo.Config.Compression != NoCompression, + } +} + // Status retrieves the status and configuration of a bucket func (kv *kvs) Status() (KeyValueStatus, error) { nfo, err := kv.js.StreamInfo(kv.stream) diff --git a/vendor/github.com/nats-io/nats.go/nats.go b/vendor/github.com/nats-io/nats.go/nats.go index c5694697106..56ad8c6c998 100644 --- a/vendor/github.com/nats-io/nats.go/nats.go +++ b/vendor/github.com/nats-io/nats.go/nats.go @@ -48,7 +48,7 @@ import ( // Default Constants const ( - Version = "1.48.0" + Version = "1.49.0" DefaultURL = "nats://127.0.0.1:4222" DefaultPort = 4222 DefaultMaxReconnect = 60 @@ -152,6 +152,8 @@ var ( ErrConnectionNotTLS = errors.New("nats: connection is not tls") ErrMaxSubscriptionsExceeded = errors.New("nats: server maximum subscriptions exceeded") ErrWebSocketHeadersAlreadySet = errors.New("nats: websocket connection headers already set") + ErrServerNotInPool = errors.New("nats: selected server is not in the pool") + ErrMixingWebsocketSchemes = errors.New("nats: mixing of websocket and non websocket URLs is not allowed") ) // GetDefaultOptions returns default configuration options for the client. @@ -242,7 +244,7 @@ type SignatureHandler func([]byte) ([]byte, error) // AuthTokenHandler is used to generate a new token. type AuthTokenHandler func() string -// UserInfoCB is used to pass the username and password when establishing connection. +// UserInfoCB is used to pass the username and password when establishing a connection. type UserInfoCB func() (string, string) // ReconnectDelayHandler is used to get from the user the desired @@ -254,6 +256,25 @@ type ReconnectDelayHandler func(attempts int) time.Duration // WebSocketHeadersHandler is an optional callback handler for generating token used for WebSocket connections. type WebSocketHeadersHandler func() (http.Header, error) +// ReconnectToServerHandler is used to determine the server to reconnect to during +// the reconnection process. The handler receives a snapshot of available servers +// in the pool and should return a pointer to one of the servers from the provided +// slice and a delay before attempting the connection. +// +// Return values: +// - *Server: The server to connect to. Must be a pointer to an element from the +// provided servers slice. If the returned server is not in the pool, the library +// will fire ReconnectErrCB with ErrServerNotInPool and fall back to default +// server selection. +// - time.Duration: The delay before attempting the connection. If zero, the +// connection attempt is made immediately. If non-zero, the library sleeps +// for exactly that duration before attempting. +// +// MaxReconnect limits are enforced automatically: servers exceeding the configured +// MaxReconnect attempts are removed from the pool before the handler is called. +// To disable this limit, set MaxReconnect to a negative value. +type ReconnectToServerHandler func([]Server, ServerInfo) (*Server, time.Duration) + // asyncCB is used to preserve order for async callbacks. type asyncCB struct { f func() @@ -350,7 +371,7 @@ type Options struct { // Defaults to 60. MaxReconnect int - // ReconnectWait sets the time to backoff after attempting a reconnect + // ReconnectWait sets the time to back off after attempting a reconnect // to a server that we were already connected to previously. // Defaults to 2s. ReconnectWait time.Duration @@ -377,7 +398,7 @@ type Options struct { // Defaults to 2s. Timeout time.Duration - // DrainTimeout sets the timeout for a Drain Operation to complete. + // DrainTimeout sets the timeout for a drain operation to complete. // Defaults to 30s. DrainTimeout time.Duration @@ -431,18 +452,25 @@ type Options struct { AsyncErrorCB ErrHandler // ReconnectErrCB sets the callback that is invoked whenever a - // reconnect attempt failed + // reconnect attempt fails. ReconnectErrCB ConnErrHandler + // ReconnectToServerCB is called before reconnection attempt. + // It is used to determine the server to reconnect to out of + // the list of available servers. + // If a reconnect attempt is not successful, this callback will + // be called again before the next attempt. + ReconnectToServerCB ReconnectToServerHandler + // ReconnectBufSize is the size of the backing bufio during reconnect. // Once this has been exhausted publish operations will return an error. // Defaults to 8388608 bytes (8MB). ReconnectBufSize int // SubChanLen is the size of the buffered channel used between the socket - // Go routine and the message delivery for SyncSubscriptions. + // goroutine and the message delivery for SyncSubscriptions. // NOTE: This does not affect AsyncSubscriptions which are - // dictated by PendingLimits() + // dictated by PendingLimits(). // Defaults to 65536. SubChanLen int @@ -470,7 +498,8 @@ type Options struct { // Token sets the token to be used when connecting to a server. Token string - // TokenHandler designates the function used to generate the token to be used when connecting to a server. + // TokenHandler designates the function used to generate the token + // used when connecting to a server. TokenHandler AuthTokenHandler // Dialer allows a custom net.Dialer when forming connections. @@ -532,13 +561,17 @@ type Options struct { // WebSocketConnectionHeaders is an optional http request headers to be sent with the WebSocket request. WebSocketConnectionHeaders http.Header - // WebSocketConnectionHeadersHandler is an optional callback handler for generating token used for WebSocket connections. + // WebSocketConnectionHeadersHandler is an optional callback handler for generating token used for WebSocket connections. WebSocketConnectionHeadersHandler WebSocketHeadersHandler // SkipSubjectValidation will disable publish subject validation. // NOTE: This is not recommended in general, as the performance gain is minimal // and may lead to breaking protocol. SkipSubjectValidation bool + + // IgnoreDiscoveredServers will disable adding advertised server URLs + // from INFO messages to the server pool. + IgnoreDiscoveredServers bool } const ( @@ -577,14 +610,14 @@ type Conn struct { // Modifying the configuration of a running Conn is a race. Opts Options wg sync.WaitGroup - srvPool []*srv - current *srv + srvPool []*Server + current *Server urls map[string]struct{} // Keep track of all known URLs (used by processInfo) conn net.Conn bw *natsWriter br *natsReader fch chan struct{} - info serverInfo + info ServerInfo ssid int64 subsMu sync.RWMutex subs map[int64]*Subscription @@ -815,18 +848,27 @@ type Statistics struct { Reconnects uint64 } -// Tracks individual backend servers. -type srv struct { - url *url.URL +// Server represents a server in the pool of servers that the client can connect to. +type Server struct { + URL *url.URL + Reconnects int didConnect bool - reconnects int lastErr error isImplicit bool tlsName string } -// The INFO block received from the server. -type serverInfo struct { +func (s Server) clone() Server { + c := s + if s.URL != nil { + u := *s.URL + c.URL = &u + } + return c +} + +// ServerInfo represents the information about the server that is sent in the INFO protocol message. +type ServerInfo struct { ID string `json:"server_id"` Name string `json:"server_name"` Proto int `json:"proto"` @@ -907,7 +949,7 @@ func Name(name string) Option { } } -// InProcessServer is an Option that will try to establish a direction to a NATS server +// InProcessServer is an Option that will try to establish a connection to a NATS server // running within the process instead of dialing via TCP. func InProcessServer(server InProcessConnProvider) Option { return func(o *Options) error { @@ -1087,6 +1129,19 @@ func CustomReconnectDelay(cb ReconnectDelayHandler) Option { } } +// ReconnectToServer is an Option to set a custom server selection callback +// for reconnection attempts. The callback receives a snapshot of available +// servers and must return a server from the pool to connect to and a delay +// duration before attempting the connection. +// +// See ReconnectToServerHandler for detailed documentation and usage examples. +func ReconnectToServer(cb ReconnectToServerHandler) Option { + return func(o *Options) error { + o.ReconnectToServerCB = cb + return nil + } +} + // PingInterval is an Option to set the period for client ping commands. // Defaults to 2m. func PingInterval(t time.Duration) Option { @@ -1522,7 +1577,7 @@ func WebSocketConnectionHeadersHandler(cb WebSocketHeadersHandler) Option { // By default, subject validation is performed to ensure that subjects // are valid according to NATS subject syntax (no spaces newlines and tabs). // NOTE: It is not recommended to use this option as the performance gain -// is minimal and disabling subject validation can lead breaking protocol +// is minimal and disabling subject validation can lead to breaking protocol // rules. func SkipSubjectValidation() Option { return func(o *Options) error { @@ -1531,6 +1586,15 @@ func SkipSubjectValidation() Option { } } +// IgnoreDiscoveredServers is an Option to disable adding advertised +// server URLs from INFO messages to the server pool. +func IgnoreDiscoveredServers() Option { + return func(o *Options) error { + o.IgnoreDiscoveredServers = true + return nil + } +} + // Handler processing // SetDisconnectHandler will set the disconnect event handler. @@ -1790,7 +1854,7 @@ const ( ) // Return the currently selected server -func (nc *Conn) currentServer() (int, *srv) { +func (nc *Conn) currentServer() (int, *Server) { for i, s := range nc.srvPool { if s == nil { continue @@ -1804,7 +1868,7 @@ func (nc *Conn) currentServer() (int, *srv) { // Pop the current server and put onto the end of the list. Select head of list as long // as number of reconnect attempts under MaxReconnect. -func (nc *Conn) selectNextServer() (*srv, error) { +func (nc *Conn) selectNextServer() (*Server, error) { i, s := nc.currentServer() if i < 0 { return nil, ErrNoServers @@ -1813,7 +1877,7 @@ func (nc *Conn) selectNextServer() (*srv, error) { num := len(sp) copy(sp[i:num-1], sp[i+1:num]) maxReconnect := nc.Opts.MaxReconnect - if maxReconnect < 0 || s.reconnects < maxReconnect { + if maxReconnect < 0 || s.Reconnects < maxReconnect { nc.srvPool[num-1] = s } else { nc.srvPool = sp[0 : num-1] @@ -1849,7 +1913,7 @@ const tlsScheme = "tls" // Server Options. We will randomize the server pool unless // the NoRandomize flag is set. func (nc *Conn) setupServerPool() error { - nc.srvPool = make([]*srv, 0, srvPoolSize) + nc.srvPool = make([]*Server, 0, srvPoolSize) nc.urls = make(map[string]struct{}, srvPoolSize) // Create srv objects from each url string in nc.Opts.Servers @@ -1886,7 +1950,7 @@ func (nc *Conn) setupServerPool() error { // Check for Scheme hint to move to TLS mode. for _, srv := range nc.srvPool { - if srv.url.Scheme == tlsScheme || srv.url.Scheme == wsSchemeTLS { + if srv.URL.Scheme == tlsScheme || srv.URL.Scheme == wsSchemeTLS { // FIXME(dlc), this is for all in the pool, should be case by case. nc.Opts.Secure = true if nc.Opts.TLSConfig == nil { @@ -1917,8 +1981,10 @@ func hostIsIP(u *url.URL) bool { return net.ParseIP(u.Hostname()) != nil } -// addURLToPool adds an entry to the server pool -func (nc *Conn) addURLToPool(sURL string, implicit, saveTLSName bool) error { +// parseServerURL parses a server URL string into a Server struct. +// It handles scheme defaults and port defaults. Does not validate websocket consistency. +// Returns the parsed Server and whether it's a websocket URL. +func (nc *Conn) parseServerURL(sURL string, implicit, saveTLSName bool) (*Server, error) { if !strings.Contains(sURL, "://") { sURL = fmt.Sprintf("%s://%s", nc.connScheme(), sURL) } @@ -1929,7 +1995,7 @@ func (nc *Conn) addURLToPool(sURL string, implicit, saveTLSName bool) error { for i := 0; i < 2; i++ { u, err = url.Parse(sURL) if err != nil { - return err + return nil, err } if u.Port() != "" { break @@ -1949,19 +2015,9 @@ func (nc *Conn) addURLToPool(sURL string, implicit, saveTLSName bool) error { } } - isWS := isWebsocketScheme(u) - // We don't support mix and match of websocket and non websocket URLs. - // If this is the first URL, then we accept and switch the global state - // to websocket. After that, we will know how to reject mixed URLs. - if len(nc.srvPool) == 0 { - nc.ws = isWS - } else if isWS && !nc.ws || !isWS && nc.ws { - return errors.New("mixing of websocket and non websocket URLs is not allowed") - } - var tlsName string if implicit { - curl := nc.current.url + curl := nc.current.URL // Check to see if we do not have a url.User but current connected // url does. If so copy over. if u.User == nil && curl.User != nil { @@ -1975,9 +2031,29 @@ func (nc *Conn) addURLToPool(sURL string, implicit, saveTLSName bool) error { } } - s := &srv{url: u, isImplicit: implicit, tlsName: tlsName} + s := &Server{URL: u, isImplicit: implicit, tlsName: tlsName} + return s, nil +} + +// addURLToPool adds an entry to the server pool +func (nc *Conn) addURLToPool(sURL string, implicit, saveTLSName bool) error { + s, err := nc.parseServerURL(sURL, implicit, saveTLSName) + if err != nil { + return err + } + + // We don't support mix and match of websocket and non websocket URLs. + // If this is the first URL, then we accept and switch the global state + // to websocket. After that, we will know how to reject mixed URLs. + isWS := isWebsocketScheme(s.URL) + if len(nc.srvPool) == 0 { + nc.ws = isWS + } else if isWS != nc.ws { + return ErrMixingWebsocketSchemes + } + nc.srvPool = append(nc.srvPool, s) - nc.urls[u.Host] = struct{}{} + nc.urls[s.URL.Host] = struct{}{} return nil } @@ -2173,7 +2249,7 @@ func (nc *Conn) createConn() (err error) { // We will auto-expand host names if they resolve to multiple IPs hosts := []string{} - u := nc.current.url + u := nc.current.URL if !nc.Opts.SkipHostLookup && net.ParseIP(u.Hostname()) == nil { addrs, _ := net.LookupHost(u.Hostname()) @@ -2259,7 +2335,7 @@ func (nc *Conn) makeTLSConn() error { if nc.current.tlsName != _EMPTY_ { tlsCopy.ServerName = nc.current.tlsName } else { - h, _, _ := net.SplitHostPort(nc.current.url.Host) + h, _, _ := net.SplitHostPort(nc.current.URL.Host) tlsCopy.ServerName = h } } @@ -2357,7 +2433,7 @@ func (nc *Conn) ConnectedUrl() string { if nc.status != CONNECTED { return _EMPTY_ } - return nc.current.url.String() + return nc.current.URL.String() } // ConnectedUrlRedacted reports the connected server's URL with passwords redacted @@ -2372,7 +2448,7 @@ func (nc *Conn) ConnectedUrlRedacted() string { if nc.status != CONNECTED { return _EMPTY_ } - return nc.current.url.Redacted() + return nc.current.URL.Redacted() } // ConnectedAddr returns the connected server's IP @@ -2587,7 +2663,7 @@ func (nc *Conn) connect() (bool, error) { if err == nil { nc.current.didConnect = true - nc.current.reconnects = 0 + nc.current.Reconnects = 0 nc.current.lastErr = nil break } else { @@ -2704,7 +2780,7 @@ func (nc *Conn) sendProto(proto string) { func (nc *Conn) connectProto() (string, error) { o := nc.Opts var nkey, sig, user, pass, token, ujwt string - u := nc.current.url.User + u := nc.current.URL.User if u != nil { // if no password, assume username is authToken if _, ok := u.Password(); !ok { @@ -2999,17 +3075,88 @@ func (nc *Conn) doReconnect(err error, forceReconnect bool) { } for i := 0; len(nc.srvPool) > 0; { - cur, err := nc.selectNextServer() - if err != nil { - nc.err = err - break + var err error + var cur *Server + var callbackDelay time.Duration + var useCallbackDelay bool + + if nc.Opts.ReconnectToServerCB != nil { + // Enforce MaxReconnect limits before calling the callback + maxReconnect := nc.Opts.MaxReconnect + if maxReconnect >= 0 { + // Remove servers that have exceeded MaxReconnect attempts + filtered := make([]*Server, 0, len(nc.srvPool)) + for _, srv := range nc.srvPool { + if srv != nil && srv.Reconnects < maxReconnect { + filtered = append(filtered, srv) + } + } + nc.srvPool = filtered + } + + // Check if we still have servers after filtering + if len(nc.srvPool) == 0 { + nc.err = ErrNoServers + break + } + + // Copy server values to avoid caller modifying internal state + srvVals := make([]Server, len(nc.srvPool)) + for idx, srv := range nc.srvPool { + if srv != nil { + srvVals[idx] = srv.clone() + } + } + + var selectedSrv *Server + selectedSrv, callbackDelay = nc.Opts.ReconnectToServerCB(srvVals, nc.info) + if selectedSrv != nil { + idx := slices.IndexFunc(nc.srvPool, func(srv *Server) bool { + return srv != nil && srv.URL.String() == selectedSrv.URL.String() + }) + if idx != -1 { + cur = nc.srvPool[idx] + nc.current = cur + useCallbackDelay = true + } else if reconnectErrCB := nc.Opts.ReconnectErrCB; reconnectErrCB != nil { + nc.ach.push(func() { reconnectErrCB(nc, ErrServerNotInPool) }) + } + } + } + + var doSleep bool + if cur == nil { + cur, err = nc.selectNextServer() + if err != nil { + nc.err = err + break + } + doSleep = i+1 >= len(nc.srvPool) && !forceReconnect } - doSleep := i+1 >= len(nc.srvPool) && !forceReconnect forceReconnect = false nc.mu.Unlock() - if !doSleep { + if useCallbackDelay { + if callbackDelay > 0 { + i = 0 + if rt == nil { + rt = time.NewTimer(callbackDelay) + } else { + rt.Reset(callbackDelay) + } + select { + case <-rqch: + rt.Stop() + nc.mu.Lock() + nc.rqch = make(chan struct{}) + nc.mu.Unlock() + case <-rt.C: + } + } else { + runtime.Gosched() + } + } else if !doSleep { i++ // Release the lock to give a chance to a concurrent nc.Close() to break the loop. runtime.Gosched() @@ -3055,7 +3202,7 @@ func (nc *Conn) doReconnect(err error, forceReconnect bool) { } // Mark that we tried a reconnect - cur.reconnects++ + cur.Reconnects++ // Try to create a new connection err = nc.createConn() @@ -3090,7 +3237,7 @@ func (nc *Conn) doReconnect(err error, forceReconnect bool) { // Clear out server stats for the server we connected to.. cur.didConnect = true - cur.reconnects = 0 + cur.Reconnects = 0 // Send existing subscription state nc.resendSubscriptions() @@ -3758,7 +3905,7 @@ func (nc *Conn) processInfo(info string) error { if info == _EMPTY_ { return nil } - var ncInfo serverInfo + var ncInfo ServerInfo if err := json.Unmarshal([]byte(info), &ncInfo); err != nil { return err } @@ -3769,7 +3916,7 @@ func (nc *Conn) processInfo(info string) error { // if advertise is disabled on that server, or servers that // did not include themselves in the async INFO protocol. // If empty, do not remove the implicit servers from the pool. - if len(nc.info.ConnectURLs) == 0 { + if len(nc.info.ConnectURLs) == 0 || nc.Opts.IgnoreDiscoveredServers { if !nc.initc && ncInfo.LameDuckMode && nc.Opts.LameDuckModeHandler != nil { nc.ach.push(func() { nc.Opts.LameDuckModeHandler(nc) }) } @@ -3792,7 +3939,7 @@ func (nc *Conn) processInfo(info string) error { sp := nc.srvPool for i := 0; i < len(sp); i++ { srv := sp[i] - curl := srv.url.Host + curl := srv.URL.Host // Check if this URL is in the INFO protocol _, inInfo := tmp[curl] // Remove from the temp map so that at the end we are left with only @@ -3800,7 +3947,7 @@ func (nc *Conn) processInfo(info string) error { delete(tmp, curl) // Keep servers that were set through Options, but also the one that // we are currently connected to (even if it is a discovered server). - if !srv.isImplicit || srv.url == nc.current.url { + if !srv.isImplicit || srv.URL == nc.current.URL { continue } if !inInfo { @@ -3812,7 +3959,7 @@ func (nc *Conn) processInfo(info string) error { } } // Figure out if we should save off the current non-IP hostname if we encounter a bare IP. - saveTLS := nc.current != nil && !hostIsIP(nc.current.url) + saveTLS := nc.current != nil && !hostIsIP(nc.current.URL) // If there are any left in the tmp map, these are new (or restarted) servers // and need to be added to the pool. @@ -4765,7 +4912,7 @@ func (s *Subscription) IsValid() bool { // // For a JetStream subscription, if the library has created the JetStream // consumer, the library will send a DeleteConsumer request to the server -// when the Drain operation completes. If a failure occurs when deleting +// when the drain operation completes. If a failure occurs when deleting // the JetStream consumer, an error will be reported to the asynchronous // error callback. // If you do not wish the JetStream consumer to be automatically deleted, @@ -5915,7 +6062,7 @@ func (nc *Conn) getServers(implicitOnly bool) []string { if implicitOnly && !nc.srvPool[i].isImplicit { continue } - url := nc.srvPool[i].url + url := nc.srvPool[i].URL servers = append(servers, fmt.Sprintf("%s://%s", url.Scheme, url.Host)) } return servers @@ -6071,6 +6218,103 @@ func (nc *Conn) Barrier(f func()) error { return nil } +// ServerPool returns a copy of the current server pool for the connection. +// +// This function should not be called from within connection callbacks to avoid +// potential deadlocks. +func (nc *Conn) ServerPool() []Server { + nc.mu.RLock() + defer nc.mu.RUnlock() + servers := make([]Server, len(nc.srvPool)) + for i, srv := range nc.srvPool { + if srv != nil { + // Return a copy to avoid exposing internal state + servers[i] = srv.clone() + } + } + return servers +} + +// SetServerPool allows updating the server pool for the connection. This +// replaces the existing pool with the provided list of server URLs. If the +// current server is not in the new pool, the client will switch to a server in +// the new pool on the next reconnect attempt. This function is thread-safe and +// can be called while the connection is active. It will return an error if the +// connection is closed or if any of the provided URLs are invalid. +// +// This function does not trigger an immediate reconnect. The new server +// pool will be used on the next reconnect attempt. +// If you want to trigger an immediate reconnect to apply the new server pool, +// you can call [Conn.ForceReconnect] after this function. +// +// Unless [IgnoreDiscoveredServers] is used true, the client will continue to +// discover and add new servers to the pool as it receives INFO messages from +// the server. If you want to prevent this behavior and only use the servers +// provided in SetServerPool, use [IgnoreDiscoveredServers]. +// +// This function should not be called from within connection callbacks to avoid +// potential deadlocks. +func (nc *Conn) SetServerPool(servers []string) error { + nc.mu.Lock() + defer nc.mu.Unlock() + if nc.isClosed() { + return ErrConnectionClosed + } + + // Parse and validate all URLs first (without modifying state) + newPool := make([]*Server, 0, len(servers)) + newURLs := make(map[string]struct{}) + + for _, addr := range servers { + s, err := nc.parseServerURL(addr, false, false) + if err != nil { + return err + } + if isWebsocketScheme(s.URL) != nc.ws { + return ErrMixingWebsocketSchemes + } + + newPool = append(newPool, s) + newURLs[s.URL.Host] = struct{}{} + } + + // Preserve state from existing pool entries + for _, newSrv := range newPool { + if idx := slices.IndexFunc(nc.srvPool, func(oldSrv *Server) bool { + return oldSrv != nil && oldSrv.URL.String() == newSrv.URL.String() + }); idx != -1 { + newSrv.Reconnects = nc.srvPool[idx].Reconnects + newSrv.didConnect = nc.srvPool[idx].didConnect + newSrv.lastErr = nc.srvPool[idx].lastErr + } + } + + nc.srvPool = newPool + nc.urls = newURLs + + // Update nc.current to point to the corresponding server in the new pool + // This is important because currentServer() uses pointer equality + if nc.current != nil { + currentURL := nc.current.URL.String() + found := false + for _, s := range newPool { + if s.URL.String() == currentURL { + // Update nc.current to point to the server instance in the new pool + nc.current = s + found = true + break + } + } + if !found && len(newPool) > 0 { + // Current server not in new pool - point to first server in new pool + // This ensures selectNextServer() can find it and properly rotate + nc.current = newPool[0] + } + } + + return nil +} + // GetClientIP returns the client IP as known by the server. // Supported as of server version 2.1.6. func (nc *Conn) GetClientIP() (net.IP, error) { @@ -6133,8 +6377,8 @@ func (nc *Conn) RemoveStatusListener(ch chan (Status)) { } for _, listeners := range nc.statListeners { - for l := range listeners { - delete(listeners, l) + for range listeners { + delete(listeners, ch) } } } diff --git a/vendor/github.com/nats-io/nkeys/.goreleaser.yml b/vendor/github.com/nats-io/nkeys/.goreleaser.yml index e5c4f154a4f..cf9d92487b5 100644 --- a/vendor/github.com/nats-io/nkeys/.goreleaser.yml +++ b/vendor/github.com/nats-io/nkeys/.goreleaser.yml @@ -1,3 +1,4 @@ +version: 2 project_name: nkeys release: github: @@ -41,7 +42,8 @@ archives: - name_template: '{{ .ProjectName }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' wrap_in_directory: true - format: zip + formats: + - zip files: - README.md - LICENSE @@ -50,7 +52,7 @@ checksum: name_template: '{{ .ProjectName }}-v{{ .Version }}-checksums.txt' snapshot: - name_template: 'dev' + version_template: dev nfpms: - file_name_template: '{{ .ProjectName }}-v{{ .Version }}-{{ .Arch }}{{ if .Arm diff --git a/vendor/github.com/russellhaering/goxmldsig/canonicalize.go b/vendor/github.com/russellhaering/goxmldsig/canonicalize.go index 4d51f4a0866..aaeb9bd5a53 100644 --- a/vendor/github.com/russellhaering/goxmldsig/canonicalize.go +++ b/vendor/github.com/russellhaering/goxmldsig/canonicalize.go @@ -1,6 +1,7 @@ package dsig import ( + "maps" "sort" "github.com/beevik/etree" @@ -164,9 +165,7 @@ func canonicalPrep(el *etree.Element, strip bool, comments bool) *etree.Element func canonicalPrepInner(el *etree.Element, seenSoFar map[string]string, strip bool, comments bool) *etree.Element { _seenSoFar := make(map[string]string) - for k, v := range seenSoFar { - _seenSoFar[k] = v - } + maps.Copy(_seenSoFar, seenSoFar) ne := el.Copy() sort.Sort(etreeutils.SortedAttrs(ne.Attr)) diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go index 6a5be036304..a5b3c75c77a 100644 --- a/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go +++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go @@ -3,6 +3,7 @@ package etreeutils import ( "errors" "fmt" + "maps" "sort" "github.com/beevik/etree" @@ -63,9 +64,7 @@ func (ctx NSContext) CheckLimit() error { func (ctx NSContext) Copy() NSContext { prefixes := make(map[string]string, len(ctx.prefixes)+4) - for k, v := range ctx.prefixes { - prefixes[k] = v - } + maps.Copy(prefixes, ctx.prefixes) return NSContext{prefixes: prefixes, limit: ctx.limit} } @@ -127,9 +126,7 @@ func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) { // Prefixes returns a copy of this context's prefix map. func (ctx NSContext) Prefixes() map[string]string { prefixes := make(map[string]string, len(ctx.prefixes)) - for k, v := range ctx.prefixes { - prefixes[k] = v - } + maps.Copy(prefixes, ctx.prefixes) return prefixes } diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go index b1fecf85a4c..0bac3187418 100644 --- a/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go +++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go @@ -9,7 +9,7 @@ import ( // NSUnmarshalElement unmarshals the passed etree Element into the value pointed to by // v using encoding/xml in the context of the passed NSContext. If v implements // ElementKeeper, SetUnderlyingElement will be called on v with a reference to el. -func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error { +func NSUnmarshalElement(ctx NSContext, el *etree.Element, v any) error { detatched, err := NSDetatch(ctx, el) if err != nil { return err diff --git a/vendor/github.com/russellhaering/goxmldsig/validate.go b/vendor/github.com/russellhaering/goxmldsig/validate.go index e64891ccc3c..5056bb9ad72 100644 --- a/vendor/github.com/russellhaering/goxmldsig/validate.go +++ b/vendor/github.com/russellhaering/goxmldsig/validate.go @@ -306,9 +306,10 @@ func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Si var ref *types.Reference // Find the first reference which references the top-level element - for _, _ref := range signedInfo.References { - if _ref.URI == "" || _ref.URI[1:] == idAttr { - ref = &_ref + for i := range signedInfo.References { + if signedInfo.References[i].URI == "" || signedInfo.References[i].URI[1:] == idAttr { + ref = &signedInfo.References[i] + break } } diff --git a/vendor/golang.org/x/crypto/ssh/agent/server.go b/vendor/golang.org/x/crypto/ssh/agent/server.go index 4e8ff86b619..2a7658cf75c 100644 --- a/vendor/golang.org/x/crypto/ssh/agent/server.go +++ b/vendor/golang.org/x/crypto/ssh/agent/server.go @@ -36,7 +36,7 @@ func (s *server) processRequestBytes(reqData []byte) []byte { return []byte{agentFailure} } - if err == nil && rep == nil { + if rep == nil { return []byte{agentSuccess} } diff --git a/vendor/golang.org/x/image/tiff/buffer.go b/vendor/golang.org/x/image/tiff/buffer.go index d1801be48ec..aaf1b06bf1e 100644 --- a/vendor/golang.org/x/image/tiff/buffer.go +++ b/vendor/golang.org/x/image/tiff/buffer.go @@ -4,7 +4,10 @@ package tiff -import "io" +import ( + "io" + "slices" +) // buffer buffers an io.Reader to satisfy io.ReaderAt. type buffer struct { @@ -12,24 +15,19 @@ type buffer struct { buf []byte } +const fillChunkSize = 10 << 20 // 10 MB + // fill reads data from b.r until the buffer contains at least end bytes. func (b *buffer) fill(end int) error { m := len(b.buf) - if end > m { - if end > cap(b.buf) { - newcap := 1024 - for newcap < end { - newcap *= 2 - } - newbuf := make([]byte, end, newcap) - copy(newbuf, b.buf) - b.buf = newbuf - } else { - b.buf = b.buf[:end] - } - if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil { - end = m + n - b.buf = b.buf[:end] + for m < end { + next := min(end-m, fillChunkSize) + b.buf = slices.Grow(b.buf, next) + b.buf = b.buf[:m+next] + n, err := io.ReadFull(b.r, b.buf[m:m+next]) + m += n + b.buf = b.buf[:m] + if err != nil { return err } } @@ -44,7 +42,8 @@ func (b *buffer) ReadAt(p []byte, off int64) (int, error) { } err := b.fill(end) - return copy(p, b.buf[o:end]), err + end = min(end, len(b.buf)) + return copy(p, b.buf[min(o, end):end]), err } // Slice returns a slice of the underlying buffer. The slice contains diff --git a/vendor/golang.org/x/image/webp/decode.go b/vendor/golang.org/x/image/webp/decode.go index 0df3c67e881..2371808f421 100644 --- a/vendor/golang.org/x/image/webp/decode.go +++ b/vendor/golang.org/x/image/webp/decode.go @@ -103,7 +103,7 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) { return m, image.Config{}, nil case fccVP8L: - if wantAlpha || alpha != nil { + if alpha != nil { return nil, image.Config{}, errInvalidFormat } if configOnly { diff --git a/vendor/golang.org/x/net/html/iter.go b/vendor/golang.org/x/net/html/iter.go index 54be8fd30fd..349ef73e64e 100644 --- a/vendor/golang.org/x/net/html/iter.go +++ b/vendor/golang.org/x/net/html/iter.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.23 - package html import "iter" diff --git a/vendor/golang.org/x/net/html/node.go b/vendor/golang.org/x/net/html/node.go index 77741a1950e..253e4679c71 100644 --- a/vendor/golang.org/x/net/html/node.go +++ b/vendor/golang.org/x/net/html/node.go @@ -11,6 +11,7 @@ import ( // A NodeType is the type of a Node. type NodeType uint32 +//go:generate stringer -type NodeType const ( ErrorNode NodeType = iota TextNode diff --git a/vendor/golang.org/x/net/html/nodetype_string.go b/vendor/golang.org/x/net/html/nodetype_string.go new file mode 100644 index 00000000000..8253af4915f --- /dev/null +++ b/vendor/golang.org/x/net/html/nodetype_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type NodeType"; DO NOT EDIT. + +package html + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ErrorNode-0] + _ = x[TextNode-1] + _ = x[DocumentNode-2] + _ = x[ElementNode-3] + _ = x[CommentNode-4] + _ = x[DoctypeNode-5] + _ = x[RawNode-6] + _ = x[scopeMarkerNode-7] +} + +const _NodeType_name = "ErrorNodeTextNodeDocumentNodeElementNodeCommentNodeDoctypeNodeRawNodescopeMarkerNode" + +var _NodeType_index = [...]uint8{0, 9, 17, 29, 40, 51, 62, 69, 84} + +func (i NodeType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_NodeType_index)-1 { + return "NodeType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _NodeType_name[_NodeType_index[idx]:_NodeType_index[idx+1]] +} diff --git a/vendor/golang.org/x/net/http2/client_priority_go126.go b/vendor/golang.org/x/net/http2/client_priority_go126.go new file mode 100644 index 00000000000..80af000b094 --- /dev/null +++ b/vendor/golang.org/x/net/http2/client_priority_go126.go @@ -0,0 +1,20 @@ +// Copyright 2026 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. + +//go:build !go1.27 + +package http2 + +import "net/http" + +// Support for go.dev/issue/75500 is added in Go 1.27. In case anyone uses +// x/net with versions before Go 1.27, we return true here so that their write +// scheduler will still be the round-robin write scheduler rather than the RFC +// 9218 write scheduler. That way, older users of Go will not see a sudden +// change of behavior just from importing x/net. +// +// TODO(nsh): remove this file after x/net go.mod is at Go 1.27. +func clientPriorityDisabled(_ *http.Server) bool { + return true +} diff --git a/vendor/golang.org/x/net/http2/client_priority_go127.go b/vendor/golang.org/x/net/http2/client_priority_go127.go new file mode 100644 index 00000000000..817d01b0573 --- /dev/null +++ b/vendor/golang.org/x/net/http2/client_priority_go127.go @@ -0,0 +1,13 @@ +// Copyright 2026 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. + +//go:build go1.27 + +package http2 + +import "net/http" + +func clientPriorityDisabled(s *http.Server) bool { + return s.DisableClientPriority +} diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index 9a4bd123c95..be75badcc0e 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -11,11 +11,13 @@ import ( "fmt" "io" "log" + "slices" "strings" "sync" "golang.org/x/net/http/httpguts" "golang.org/x/net/http2/hpack" + "golang.org/x/net/internal/httpsfv" ) const frameHeaderLen = 9 @@ -23,33 +25,36 @@ const frameHeaderLen = 9 var padZeros = make([]byte, 255) // zeros for padding // A FrameType is a registered frame type as defined in -// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 +// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 and other future +// RFCs. type FrameType uint8 const ( - FrameData FrameType = 0x0 - FrameHeaders FrameType = 0x1 - FramePriority FrameType = 0x2 - FrameRSTStream FrameType = 0x3 - FrameSettings FrameType = 0x4 - FramePushPromise FrameType = 0x5 - FramePing FrameType = 0x6 - FrameGoAway FrameType = 0x7 - FrameWindowUpdate FrameType = 0x8 - FrameContinuation FrameType = 0x9 + FrameData FrameType = 0x0 + FrameHeaders FrameType = 0x1 + FramePriority FrameType = 0x2 + FrameRSTStream FrameType = 0x3 + FrameSettings FrameType = 0x4 + FramePushPromise FrameType = 0x5 + FramePing FrameType = 0x6 + FrameGoAway FrameType = 0x7 + FrameWindowUpdate FrameType = 0x8 + FrameContinuation FrameType = 0x9 + FramePriorityUpdate FrameType = 0x10 ) var frameNames = [...]string{ - FrameData: "DATA", - FrameHeaders: "HEADERS", - FramePriority: "PRIORITY", - FrameRSTStream: "RST_STREAM", - FrameSettings: "SETTINGS", - FramePushPromise: "PUSH_PROMISE", - FramePing: "PING", - FrameGoAway: "GOAWAY", - FrameWindowUpdate: "WINDOW_UPDATE", - FrameContinuation: "CONTINUATION", + FrameData: "DATA", + FrameHeaders: "HEADERS", + FramePriority: "PRIORITY", + FrameRSTStream: "RST_STREAM", + FrameSettings: "SETTINGS", + FramePushPromise: "PUSH_PROMISE", + FramePing: "PING", + FrameGoAway: "GOAWAY", + FrameWindowUpdate: "WINDOW_UPDATE", + FrameContinuation: "CONTINUATION", + FramePriorityUpdate: "PRIORITY_UPDATE", } func (t FrameType) String() string { @@ -125,21 +130,24 @@ var flagName = map[FrameType]map[Flags]string{ type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) var frameParsers = [...]frameParser{ - FrameData: parseDataFrame, - FrameHeaders: parseHeadersFrame, - FramePriority: parsePriorityFrame, - FrameRSTStream: parseRSTStreamFrame, - FrameSettings: parseSettingsFrame, - FramePushPromise: parsePushPromise, - FramePing: parsePingFrame, - FrameGoAway: parseGoAwayFrame, - FrameWindowUpdate: parseWindowUpdateFrame, - FrameContinuation: parseContinuationFrame, + FrameData: parseDataFrame, + FrameHeaders: parseHeadersFrame, + FramePriority: parsePriorityFrame, + FrameRSTStream: parseRSTStreamFrame, + FrameSettings: parseSettingsFrame, + FramePushPromise: parsePushPromise, + FramePing: parsePingFrame, + FrameGoAway: parseGoAwayFrame, + FrameWindowUpdate: parseWindowUpdateFrame, + FrameContinuation: parseContinuationFrame, + FramePriorityUpdate: parsePriorityUpdateFrame, } func typeFrameParser(t FrameType) frameParser { if int(t) < len(frameParsers) { - return frameParsers[t] + if f := frameParsers[t]; f != nil { + return f + } } return parseUnknownFrame } @@ -1180,9 +1188,34 @@ type PriorityFrame struct { PriorityParam } -var defaultRFC9218Priority = PriorityParam{ - incremental: 0, - urgency: 3, +// defaultRFC9218Priority determines what priority we should use as the default +// value. +// +// According to RFC 9218, by default, streams should be given an urgency of 3 +// and should be non-incremental. However, making streams non-incremental by +// default would be a huge change to our historical behavior where we would +// round-robin writes across streams. When streams are non-incremental, we +// would process streams of the same urgency one-by-one to completion instead. +// +// To avoid such a sudden change which might break some HTTP/2 users, this +// function allows the caller to specify whether they can actually use the +// default value as specified in RFC 9218. If not, this function will return a +// priority value where streams are incremental by default instead: effectively +// a round-robin between stream of the same urgency. +// +// As an example, a server might not be able to use the RFC 9218 default value +// when it's not sure that the client it is serving is aware of RFC 9218. +func defaultRFC9218Priority(canUseDefault bool) PriorityParam { + if canUseDefault { + return PriorityParam{ + urgency: 3, + incremental: 0, + } + } + return PriorityParam{ + urgency: 3, + incremental: 1, + } } // Note that HTTP/2 has had two different prioritization schemes, and @@ -1266,6 +1299,74 @@ func (f *Framer) WritePriority(streamID uint32, p PriorityParam) error { return f.endWrite() } +// PriorityUpdateFrame is a PRIORITY_UPDATE frame as described in +// https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame. +type PriorityUpdateFrame struct { + FrameHeader + Priority string + PrioritizedStreamID uint32 +} + +func parseRFC9218Priority(s string, canUseDefault bool) (p PriorityParam, ok bool) { + p = defaultRFC9218Priority(canUseDefault) + ok = httpsfv.ParseDictionary(s, func(key, val, _ string) { + switch key { + case "u": + if u, ok := httpsfv.ParseInteger(val); ok && u >= 0 && u <= 7 { + p.urgency = uint8(u) + } + case "i": + if i, ok := httpsfv.ParseBoolean(val); ok { + if i { + p.incremental = 1 + } else { + p.incremental = 0 + } + } + } + }) + if !ok { + return defaultRFC9218Priority(canUseDefault), ok + } + return p, true +} + +func parsePriorityUpdateFrame(_ *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) { + if fh.StreamID != 0 { + countError("frame_priority_update_non_zero_stream") + return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with non-zero stream ID"} + } + if len(payload) < 4 { + countError("frame_priority_update_bad_length") + return nil, connError{ErrCodeFrameSize, fmt.Sprintf("PRIORITY_UPDATE frame payload size was %d; want at least 4", len(payload))} + } + v := binary.BigEndian.Uint32(payload[:4]) + streamID := v & 0x7fffffff // mask off high bit + if streamID == 0 { + countError("frame_priority_update_prioritizing_zero_stream") + return nil, connError{ErrCodeProtocol, "PRIORITY_UPDATE frame with prioritized stream ID of zero"} + } + return &PriorityUpdateFrame{ + FrameHeader: fh, + PrioritizedStreamID: streamID, + Priority: string(payload[4:]), + }, nil +} + +// WritePriorityUpdate writes a PRIORITY_UPDATE frame. +// +// It will perform exactly one Write to the underlying Writer. +// It is the caller's responsibility to not call other Write methods concurrently. +func (f *Framer) WritePriorityUpdate(streamID uint32, priority string) error { + if !validStreamID(streamID) && !f.AllowIllegalWrites { + return errStreamID + } + f.startWrite(FramePriorityUpdate, 0, 0) + f.writeUint32(streamID) + f.writeBytes([]byte(priority)) + return f.endWrite() +} + // A RSTStreamFrame allows for abnormal termination of a stream. // See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4 type RSTStreamFrame struct { @@ -1547,6 +1648,23 @@ func (mh *MetaHeadersFrame) PseudoFields() []hpack.HeaderField { return mh.Fields } +func (mh *MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p PriorityParam, priorityAwareAfter, hasIntermediary bool) { + var s string + for _, field := range mh.Fields { + if field.Name == "priority" { + s = field.Value + priorityAware = true + } + if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) { + hasIntermediary = true + } + } + // No need to check for ok. parseRFC9218Priority will return a default + // value if there is no priority field or if the field cannot be parsed. + p, _ = parseRFC9218Priority(s, priorityAware && !hasIntermediary) + return p, priorityAware, hasIntermediary +} + func (mh *MetaHeadersFrame) checkPseudos() error { var isRequest, isResponse bool pf := mh.PseudoFields() diff --git a/vendor/golang.org/x/net/http2/http2.go b/vendor/golang.org/x/net/http2/http2.go index 105fe12fefa..6320f4eb4c1 100644 --- a/vendor/golang.org/x/net/http2/http2.go +++ b/vendor/golang.org/x/net/http2/http2.go @@ -169,6 +169,7 @@ const ( SettingMaxFrameSize SettingID = 0x5 SettingMaxHeaderListSize SettingID = 0x6 SettingEnableConnectProtocol SettingID = 0x8 + SettingNoRFC7540Priorities SettingID = 0x9 ) var settingName = map[SettingID]string{ @@ -179,6 +180,7 @@ var settingName = map[SettingID]string{ SettingMaxFrameSize: "MAX_FRAME_SIZE", SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE", SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL", + SettingNoRFC7540Priorities: "NO_RFC7540_PRIORITIES", } func (s SettingID) String() string { diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index bdc5520ebd3..7ef807f79df 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -472,10 +472,13 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon sc.conn.SetWriteDeadline(time.Time{}) } - if s.NewWriteScheduler != nil { + switch { + case s.NewWriteScheduler != nil: sc.writeSched = s.NewWriteScheduler() - } else { + case clientPriorityDisabled(http1srv): sc.writeSched = newRoundRobinWriteScheduler() + default: + sc.writeSched = newPriorityWriteSchedulerRFC9218() } // These start at the RFC-specified defaults. If there is a higher @@ -648,6 +651,23 @@ type serverConn struct { // Used by startGracefulShutdown. shutdownOnce sync.Once + + // Used for RFC 9218 prioritization. + hasIntermediary bool // connection is done via an intermediary / proxy + priorityAware bool // the client has sent priority signal, meaning that it is aware of it. +} + +func (sc *serverConn) writeSchedIgnoresRFC7540() bool { + switch sc.writeSched.(type) { + case *priorityWriteSchedulerRFC9218: + return true + case *randomWriteScheduler: + return true + case *roundRobinWriteScheduler: + return true + default: + return false + } } func (sc *serverConn) maxHeaderListSize() uint32 { @@ -938,6 +958,9 @@ func (sc *serverConn) serve(conf http2Config) { if !disableExtendedConnectProtocol { settings = append(settings, Setting{SettingEnableConnectProtocol, 1}) } + if sc.writeSchedIgnoresRFC7540() { + settings = append(settings, Setting{SettingNoRFC7540Priorities, 1}) + } sc.writeFrame(FrameWriteRequest{ write: settings, }) @@ -1623,6 +1646,8 @@ func (sc *serverConn) processFrame(f Frame) error { // A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE // frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. return sc.countError("push_promise", ConnectionError(ErrCodeProtocol)) + case *PriorityUpdateFrame: + return sc.processPriorityUpdate(f) default: sc.vlogf("http2: server ignoring frame: %v", f.Header()) return nil @@ -1803,6 +1828,10 @@ func (sc *serverConn) processSetting(s Setting) error { case SettingEnableConnectProtocol: // Receipt of this parameter by a server does not // have any impact + case SettingNoRFC7540Priorities: + if s.Val > 1 { + return ConnectionError(ErrCodeProtocol) + } default: // Unknown setting: "An endpoint that receives a SETTINGS // frame with any unknown or unsupported identifier MUST @@ -2073,13 +2102,33 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { if f.StreamEnded() { initialState = stateHalfClosedRemote } - st := sc.newStream(id, 0, initialState) + + // We are handling two special cases here: + // 1. When a request is sent via an intermediary, we force priority to be + // u=3,i. This is essentially a round-robin behavior, and is done to ensure + // fairness between, for example, multiple clients using the same proxy. + // 2. Until a client has shown that it is aware of RFC 9218, we make its + // streams non-incremental by default. This is done to preserve the + // historical behavior of handling streams in a round-robin manner, rather + // than one-by-one to completion. + initialPriority := defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary) + if _, ok := sc.writeSched.(*priorityWriteSchedulerRFC9218); ok && !sc.hasIntermediary { + headerPriority, priorityAware, hasIntermediary := f.rfc9218Priority(sc.priorityAware) + initialPriority = headerPriority + sc.hasIntermediary = hasIntermediary + if priorityAware { + sc.priorityAware = true + } + } + st := sc.newStream(id, 0, initialState, initialPriority) if f.HasPriority() { if err := sc.checkPriority(f.StreamID, f.Priority); err != nil { return err } - sc.writeSched.AdjustStream(st.id, f.Priority) + if !sc.writeSchedIgnoresRFC7540() { + sc.writeSched.AdjustStream(st.id, f.Priority) + } } rw, req, err := sc.newWriterAndRequest(st, f) @@ -2120,7 +2169,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) { sc.serveG.check() id := uint32(1) sc.maxClientStreamID = id - st := sc.newStream(id, 0, stateHalfClosedRemote) + st := sc.newStream(id, 0, stateHalfClosedRemote, defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)) st.reqTrailer = req.Trailer if st.reqTrailer != nil { st.trailer = make(http.Header) @@ -2185,11 +2234,32 @@ func (sc *serverConn) processPriority(f *PriorityFrame) error { if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil { return err } + // We need to avoid calling AdjustStream when using the RFC 9218 write + // scheduler. Otherwise, incremental's zero value in PriorityParam will + // unexpectedly make all streams non-incremental. This causes us to process + // streams one-by-one to completion rather than doing it in a round-robin + // manner (the historical behavior), which might be unexpected to users. + if sc.writeSchedIgnoresRFC7540() { + return nil + } sc.writeSched.AdjustStream(f.StreamID, f.PriorityParam) return nil } -func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream { +func (sc *serverConn) processPriorityUpdate(f *PriorityUpdateFrame) error { + sc.priorityAware = true + if _, ok := sc.writeSched.(*priorityWriteSchedulerRFC9218); !ok { + return nil + } + p, ok := parseRFC9218Priority(f.Priority, sc.priorityAware) + if !ok { + return sc.countError("unparsable_priority_update", streamError(f.PrioritizedStreamID, ErrCodeProtocol)) + } + sc.writeSched.AdjustStream(f.PrioritizedStreamID, p) + return nil +} + +func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority PriorityParam) *stream { sc.serveG.check() if id == 0 { panic("internal error: cannot create stream with id 0") @@ -2212,7 +2282,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream } sc.streams[id] = st - sc.writeSched.OpenStream(st.id, OpenStreamOptions{PusherID: pusherID}) + sc.writeSched.OpenStream(st.id, OpenStreamOptions{PusherID: pusherID, priority: priority}) if st.isPushed() { sc.curPushedStreams++ } else { @@ -3218,7 +3288,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) { // transition to "half closed (remote)" after sending the initial HEADERS, but // we start in "half closed (remote)" for simplicity. // See further comments at the definition of stateHalfClosedRemote. - promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote) + promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote, defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)) rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{ Method: msg.method, Scheme: msg.url.Scheme, diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index ccb87e6da37..8cf64b78e28 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -2779,6 +2779,11 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) { cs.abortStream(err) } +func (rl *clientConnReadLoop) endStreamErrorLocked(cs *clientStream, err error) { + cs.readAborted = true + cs.abortStreamLocked(err) +} + // Constants passed to streamByID for documentation purposes. const ( headerOrDataFrame = true @@ -2946,7 +2951,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { if !fl.add(int32(f.Increment)) { // For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR if cs != nil { - rl.endStreamError(cs, StreamError{ + rl.endStreamErrorLocked(cs, StreamError{ StreamID: f.StreamID, Code: ErrCodeFlowControl, }) diff --git a/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go b/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go index 4e33c29a244..7803a9261b0 100644 --- a/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go +++ b/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go @@ -56,6 +56,10 @@ type PriorityWriteSchedulerConfig struct { // frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3. // If cfg is nil, default options are used. func NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler { + return newPriorityWriteSchedulerRFC7540(cfg) +} + +func newPriorityWriteSchedulerRFC7540(cfg *PriorityWriteSchedulerConfig) WriteScheduler { if cfg == nil { // For justification of these defaults, see: // https://docs.google.com/document/d/1oLhNg1skaWD4_DtaoCxdSRN5erEXrH-KnLrMwEpOtFY diff --git a/vendor/golang.org/x/net/internal/httpsfv/httpsfv.go b/vendor/golang.org/x/net/internal/httpsfv/httpsfv.go new file mode 100644 index 00000000000..4ae2ca5b8e3 --- /dev/null +++ b/vendor/golang.org/x/net/internal/httpsfv/httpsfv.go @@ -0,0 +1,665 @@ +// Copyright 2025 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 httpsfv provides functionality for dealing with HTTP Structured +// Field Values. +package httpsfv + +import ( + "slices" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +func isLCAlpha(b byte) bool { + return (b >= 'a' && b <= 'z') +} + +func isAlpha(b byte) bool { + return isLCAlpha(b) || (b >= 'A' && b <= 'Z') +} + +func isDigit(b byte) bool { + return b >= '0' && b <= '9' +} + +func isVChar(b byte) bool { + return b >= 0x21 && b <= 0x7e +} + +func isSP(b byte) bool { + return b == 0x20 +} + +func isTChar(b byte) bool { + if isAlpha(b) || isDigit(b) { + return true + } + return slices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, b) +} + +func countLeftWhitespace(s string) int { + i := 0 + for _, ch := range []byte(s) { + if ch != ' ' && ch != '\t' { + break + } + i++ + } + return i +} + +// https://www.rfc-editor.org/rfc/rfc4648#section-8. +func decOctetHex(ch1, ch2 byte) (ch byte, ok bool) { + decBase16 := func(in byte) (out byte, ok bool) { + if !isDigit(in) && !(in >= 'a' && in <= 'f') { + return 0, false + } + if isDigit(in) { + return in - '0', true + } + return in - 'a' + 10, true + } + + if ch1, ok = decBase16(ch1); !ok { + return 0, ok + } + if ch2, ok = decBase16(ch2); !ok { + return 0, ok + } + return ch1<<4 | ch2, true +} + +// ParseList parses a list from a given HTTP Structured Field Values. +// +// Given an HTTP SFV string that represents a list, it will call the given +// function using each of the members and parameters contained in the list. +// This allows the caller to extract information out of the list. +// +// This function will return once it encounters the end of the string, or +// something that is not a list. If it cannot consume the entire given +// string, the ok value returned will be false. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-list. +func ParseList(s string, f func(member, param string)) (ok bool) { + for len(s) != 0 { + var member, param string + if len(s) != 0 && s[0] == '(' { + if member, s, ok = consumeBareInnerList(s, nil); !ok { + return ok + } + } else { + if member, s, ok = consumeBareItem(s); !ok { + return ok + } + } + if param, s, ok = consumeParameter(s, nil); !ok { + return ok + } + if f != nil { + f(member, param) + } + + s = s[countLeftWhitespace(s):] + if len(s) == 0 { + break + } + if s[0] != ',' { + return false + } + s = s[1:] + s = s[countLeftWhitespace(s):] + if len(s) == 0 { + return false + } + } + return true +} + +// consumeBareInnerList consumes an inner list +// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list), +// except for the inner list's top-most parameter. +// For example, given `(a;b c;d);e`, it will consume only `(a;b c;d)`. +func consumeBareInnerList(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) { + if len(s) == 0 || s[0] != '(' { + return "", s, false + } + rest = s[1:] + for len(rest) != 0 { + var bareItem, param string + rest = rest[countLeftWhitespace(rest):] + if len(rest) != 0 && rest[0] == ')' { + rest = rest[1:] + break + } + if bareItem, rest, ok = consumeBareItem(rest); !ok { + return "", s, ok + } + if param, rest, ok = consumeParameter(rest, nil); !ok { + return "", s, ok + } + if len(rest) == 0 || (rest[0] != ')' && !isSP(rest[0])) { + return "", s, false + } + if f != nil { + f(bareItem, param) + } + } + return s[:len(s)-len(rest)], rest, true +} + +// ParseBareInnerList parses a bare inner list from a given HTTP Structured +// Field Values. +// +// We define a bare inner list as an inner list +// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list), +// without the top-most parameter of the inner list. For example, given the +// inner list `(a;b c;d);e`, the bare inner list would be `(a;b c;d)`. +// +// Given an HTTP SFV string that represents a bare inner list, it will call the +// given function using each of the bare item and parameter within the bare +// inner list. This allows the caller to extract information out of the bare +// inner list. +// +// This function will return once it encounters the end of the bare inner list, +// or something that is not a bare inner list. If it cannot consume the entire +// given string, the ok value returned will be false. +func ParseBareInnerList(s string, f func(bareItem, param string)) (ok bool) { + _, rest, ok := consumeBareInnerList(s, f) + return rest == "" && ok +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item. +func consumeItem(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) { + var bareItem, param string + if bareItem, rest, ok = consumeBareItem(s); !ok { + return "", s, ok + } + if param, rest, ok = consumeParameter(rest, nil); !ok { + return "", s, ok + } + if f != nil { + f(bareItem, param) + } + return s[:len(s)-len(rest)], rest, true +} + +// ParseItem parses an item from a given HTTP Structured Field Values. +// +// Given an HTTP SFV string that represents an item, it will call the given +// function once, with the bare item and the parameter of the item. This allows +// the caller to extract information out of the item. +// +// This function will return once it encounters the end of the string, or +// something that is not an item. If it cannot consume the entire given +// string, the ok value returned will be false. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item. +func ParseItem(s string, f func(bareItem, param string)) (ok bool) { + _, rest, ok := consumeItem(s, f) + return rest == "" && ok +} + +// ParseDictionary parses a dictionary from a given HTTP Structured Field +// Values. +// +// Given an HTTP SFV string that represents a dictionary, it will call the +// given function using each of the keys, values, and parameters contained in +// the dictionary. This allows the caller to extract information out of the +// dictionary. +// +// This function will return once it encounters the end of the string, or +// something that is not a dictionary. If it cannot consume the entire given +// string, the ok value returned will be false. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-dictionary. +func ParseDictionary(s string, f func(key, val, param string)) (ok bool) { + for len(s) != 0 { + var key, val, param string + val = "?1" // Default value for empty val is boolean true. + if key, s, ok = consumeKey(s); !ok { + return ok + } + if len(s) != 0 && s[0] == '=' { + s = s[1:] + if len(s) != 0 && s[0] == '(' { + if val, s, ok = consumeBareInnerList(s, nil); !ok { + return ok + } + } else { + if val, s, ok = consumeBareItem(s); !ok { + return ok + } + } + } + if param, s, ok = consumeParameter(s, nil); !ok { + return ok + } + if f != nil { + f(key, val, param) + } + s = s[countLeftWhitespace(s):] + if len(s) == 0 { + break + } + if s[0] == ',' { + s = s[1:] + } + s = s[countLeftWhitespace(s):] + if len(s) == 0 { + return false + } + } + return true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param. +func consumeParameter(s string, f func(key, val string)) (consumed, rest string, ok bool) { + rest = s + for len(rest) != 0 { + var key, val string + val = "?1" // Default value for empty val is boolean true. + if rest[0] != ';' { + break + } + rest = rest[1:] + rest = rest[countLeftWhitespace(rest):] + key, rest, ok = consumeKey(rest) + if !ok { + return "", s, ok + } + if len(rest) != 0 && rest[0] == '=' { + rest = rest[1:] + val, rest, ok = consumeBareItem(rest) + if !ok { + return "", s, ok + } + } + if f != nil { + f(key, val) + } + } + return s[:len(s)-len(rest)], rest, true +} + +// ParseParameter parses a parameter from a given HTTP Structured Field Values. +// +// Given an HTTP SFV string that represents a parameter, it will call the given +// function using each of the keys and values contained in the parameter. This +// allows the caller to extract information out of the parameter. +// +// This function will return once it encounters the end of the string, or +// something that is not a parameter. If it cannot consume the entire given +// string, the ok value returned will be false. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param. +func ParseParameter(s string, f func(key, val string)) (ok bool) { + _, rest, ok := consumeParameter(s, f) + return rest == "" && ok +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-key. +func consumeKey(s string) (consumed, rest string, ok bool) { + if len(s) == 0 || (!isLCAlpha(s[0]) && s[0] != '*') { + return "", s, false + } + i := 0 + for _, ch := range []byte(s) { + if !isLCAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("_-.*"), ch) { + break + } + i++ + } + return s[:i], s[i:], true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim. +func consumeIntegerOrDecimal(s string) (consumed, rest string, ok bool) { + var i, signOffset, periodIndex int + var isDecimal bool + if i < len(s) && s[i] == '-' { + i++ + signOffset++ + } + if i >= len(s) { + return "", s, false + } + if !isDigit(s[i]) { + return "", s, false + } + for i < len(s) { + ch := s[i] + if isDigit(ch) { + i++ + continue + } + if !isDecimal && ch == '.' { + if i-signOffset > 12 { + return "", s, false + } + periodIndex = i + isDecimal = true + i++ + continue + } + break + } + if !isDecimal && i-signOffset > 15 { + return "", s, false + } + if isDecimal { + if i-signOffset > 16 { + return "", s, false + } + if s[i-1] == '.' { + return "", s, false + } + if i-periodIndex-1 > 3 { + return "", s, false + } + } + return s[:i], s[i:], true +} + +// ParseInteger parses an integer from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid integer. It returns the +// parsed integer and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim. +func ParseInteger(s string) (parsed int64, ok bool) { + if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" { + return 0, false + } + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + return n, true + } + return 0, false +} + +// ParseDecimal parses a decimal from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid decimal. It returns the +// parsed decimal and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim. +func ParseDecimal(s string) (parsed float64, ok bool) { + if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" { + return 0, false + } + if !strings.Contains(s, ".") { + return 0, false + } + if n, err := strconv.ParseFloat(s, 64); err == nil { + return n, true + } + return 0, false +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string. +func consumeString(s string) (consumed, rest string, ok bool) { + if len(s) == 0 || s[0] != '"' { + return "", s, false + } + for i := 1; i < len(s); i++ { + switch ch := s[i]; ch { + case '\\': + if i+1 >= len(s) { + return "", s, false + } + i++ + if ch = s[i]; ch != '"' && ch != '\\' { + return "", s, false + } + case '"': + return s[:i+1], s[i+1:], true + default: + if !isVChar(ch) && !isSP(ch) { + return "", s, false + } + } + } + return "", s, false +} + +// ParseString parses a Go string from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid string. It returns the +// parsed string and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string. +func ParseString(s string) (parsed string, ok bool) { + if _, rest, ok := consumeString(s); !ok || rest != "" { + return "", false + } + return s[1 : len(s)-1], true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token +func consumeToken(s string) (consumed, rest string, ok bool) { + if len(s) == 0 || (!isAlpha(s[0]) && s[0] != '*') { + return "", s, false + } + i := 0 + for _, ch := range []byte(s) { + if !isTChar(ch) && !slices.Contains([]byte(":/"), ch) { + break + } + i++ + } + return s[:i], s[i:], true +} + +// ParseToken parses a token from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid token. It returns the +// parsed token and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token +func ParseToken(s string) (parsed string, ok bool) { + if _, rest, ok := consumeToken(s); !ok || rest != "" { + return "", false + } + return s, true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence. +func consumeByteSequence(s string) (consumed, rest string, ok bool) { + if len(s) == 0 || s[0] != ':' { + return "", s, false + } + for i := 1; i < len(s); i++ { + if ch := s[i]; ch == ':' { + return s[:i+1], s[i+1:], true + } + if ch := s[i]; !isAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("+/="), ch) { + return "", s, false + } + } + return "", s, false +} + +// ParseByteSequence parses a byte sequence from a given HTTP Structured Field +// Values. +// +// The entire HTTP SFV string must consist of a valid byte sequence. It returns +// the parsed byte sequence and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence. +func ParseByteSequence(s string) (parsed []byte, ok bool) { + if _, rest, ok := consumeByteSequence(s); !ok || rest != "" { + return nil, false + } + return []byte(s[1 : len(s)-1]), true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean. +func consumeBoolean(s string) (consumed, rest string, ok bool) { + if len(s) >= 2 && (s[:2] == "?0" || s[:2] == "?1") { + return s[:2], s[2:], true + } + return "", s, false +} + +// ParseBoolean parses a boolean from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid boolean. It returns the +// parsed boolean and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean. +func ParseBoolean(s string) (parsed bool, ok bool) { + if _, rest, ok := consumeBoolean(s); !ok || rest != "" { + return false, false + } + return s == "?1", true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date. +func consumeDate(s string) (consumed, rest string, ok bool) { + if len(s) == 0 || s[0] != '@' { + return "", s, false + } + if _, rest, ok = consumeIntegerOrDecimal(s[1:]); !ok { + return "", s, ok + } + consumed = s[:len(s)-len(rest)] + if slices.Contains([]byte(consumed), '.') { + return "", s, false + } + return consumed, rest, ok +} + +// ParseDate parses a date from a given HTTP Structured Field Values. +// +// The entire HTTP SFV string must consist of a valid date. It returns the +// parsed date and an ok boolean value, indicating success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date. +func ParseDate(s string) (parsed time.Time, ok bool) { + if _, rest, ok := consumeDate(s); !ok || rest != "" { + return time.Time{}, false + } + if n, ok := ParseInteger(s[1:]); !ok { + return time.Time{}, false + } else { + return time.Unix(n, 0), true + } +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string. +func consumeDisplayString(s string) (consumed, rest string, ok bool) { + // To prevent excessive allocation, especially when input is large, we + // maintain a buffer of 4 bytes to keep track of the last rune we + // encounter. This way, we can validate that the display string conforms to + // UTF-8 without actually building the whole string. + var lastRune [4]byte + var runeLen int + isPartOfValidRune := func(ch byte) bool { + lastRune[runeLen] = ch + runeLen++ + if utf8.FullRune(lastRune[:runeLen]) { + r, s := utf8.DecodeRune(lastRune[:runeLen]) + if r == utf8.RuneError { + return false + } + copy(lastRune[:], lastRune[s:runeLen]) + runeLen -= s + return true + } + return runeLen <= 4 + } + + if len(s) <= 1 || s[:2] != `%"` { + return "", s, false + } + i := 2 + for i < len(s) { + ch := s[i] + if !isVChar(ch) && !isSP(ch) { + return "", s, false + } + switch ch { + case '"': + if runeLen > 0 { + return "", s, false + } + return s[:i+1], s[i+1:], true + case '%': + if i+2 >= len(s) { + return "", s, false + } + if ch, ok = decOctetHex(s[i+1], s[i+2]); !ok { + return "", s, ok + } + if ok = isPartOfValidRune(ch); !ok { + return "", s, ok + } + i += 3 + default: + if ok = isPartOfValidRune(ch); !ok { + return "", s, ok + } + i++ + } + } + return "", s, false +} + +// ParseDisplayString parses a display string from a given HTTP Structured +// Field Values. +// +// The entire HTTP SFV string must consist of a valid display string. It +// returns the parsed display string and an ok boolean value, indicating +// success or not. +// +// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string. +func ParseDisplayString(s string) (parsed string, ok bool) { + if _, rest, ok := consumeDisplayString(s); !ok || rest != "" { + return "", false + } + // consumeDisplayString() already validates that we have a valid display + // string. Therefore, we can just construct the display string, without + // validating it again. + s = s[2 : len(s)-1] + var b strings.Builder + for i := 0; i < len(s); { + if s[i] == '%' { + decoded, _ := decOctetHex(s[i+1], s[i+2]) + b.WriteByte(decoded) + i += 3 + continue + } + b.WriteByte(s[i]) + i++ + } + return b.String(), true +} + +// https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item. +func consumeBareItem(s string) (consumed, rest string, ok bool) { + if len(s) == 0 { + return "", s, false + } + ch := s[0] + switch { + case ch == '-' || isDigit(ch): + return consumeIntegerOrDecimal(s) + case ch == '"': + return consumeString(s) + case ch == '*' || isAlpha(ch): + return consumeToken(s) + case ch == ':': + return consumeByteSequence(s) + case ch == '?': + return consumeBoolean(s) + case ch == '@': + return consumeDate(s) + case ch == '%': + return consumeDisplayString(s) + default: + return "", s, false + } +} diff --git a/vendor/golang.org/x/net/internal/socket/empty.s b/vendor/golang.org/x/net/internal/socket/empty.s index 49d79791e01..c7bde71f134 100644 --- a/vendor/golang.org/x/net/internal/socket/empty.s +++ b/vendor/golang.org/x/net/internal/socket/empty.s @@ -2,6 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin && go1.12 +//go:build darwin // This exists solely so we can linkname in symbols from syscall. diff --git a/vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go b/vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go index e212b50f8d9..927ce91ac23 100644 --- a/vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go +++ b/vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go @@ -6,7 +6,10 @@ package socket -import "unsafe" +import ( + "encoding/binary" + "unsafe" +) func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) { for i := range vs { @@ -31,5 +34,5 @@ func (h *msghdr) controllen() int { } func (h *msghdr) flags() int { - return int(NativeEndian.Uint32(h.Pad_cgo_2[:])) + return int(binary.NativeEndian.Uint32(h.Pad_cgo_2[:])) } diff --git a/vendor/golang.org/x/net/internal/socket/socket.go b/vendor/golang.org/x/net/internal/socket/socket.go index dba47bf12b9..fb7fa1e00ae 100644 --- a/vendor/golang.org/x/net/internal/socket/socket.go +++ b/vendor/golang.org/x/net/internal/socket/socket.go @@ -7,6 +7,7 @@ package socket // import "golang.org/x/net/internal/socket" import ( + "encoding/binary" "errors" "net" "runtime" @@ -58,7 +59,7 @@ func (o *Option) GetInt(c *Conn) (int, error) { if o.Len == 1 { return int(b[0]), nil } - return int(NativeEndian.Uint32(b[:4])), nil + return int(binary.NativeEndian.Uint32(b[:4])), nil } // Set writes the option and value to the kernel. @@ -84,7 +85,7 @@ func (o *Option) SetInt(c *Conn, v int) error { b = []byte{byte(v)} } else { var bb [4]byte - NativeEndian.PutUint32(bb[:o.Len], uint32(v)) + binary.NativeEndian.PutUint32(bb[:o.Len], uint32(v)) b = bb[:4] } return o.set(c, b) diff --git a/vendor/golang.org/x/net/internal/socket/sys.go b/vendor/golang.org/x/net/internal/socket/sys.go deleted file mode 100644 index 4a26af18634..00000000000 --- a/vendor/golang.org/x/net/internal/socket/sys.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 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 socket - -import ( - "encoding/binary" - "unsafe" -) - -// NativeEndian is the machine native endian implementation of ByteOrder. -var NativeEndian binary.ByteOrder - -func init() { - i := uint32(1) - b := (*[4]byte)(unsafe.Pointer(&i)) - if b[0] == 1 { - NativeEndian = binary.LittleEndian - } else { - NativeEndian = binary.BigEndian - } -} diff --git a/vendor/golang.org/x/net/internal/socket/sys_posix.go b/vendor/golang.org/x/net/internal/socket/sys_posix.go index 58d86548244..84ff2351707 100644 --- a/vendor/golang.org/x/net/internal/socket/sys_posix.go +++ b/vendor/golang.org/x/net/internal/socket/sys_posix.go @@ -36,7 +36,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int { if ip4 := ip.To4(); ip4 != nil { switch runtime.GOOS { case "android", "illumos", "linux", "solaris", "windows": - NativeEndian.PutUint16(b[:2], uint16(sysAF_INET)) + binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET)) default: b[0] = sizeofSockaddrInet4 b[1] = sysAF_INET @@ -48,7 +48,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int { if ip6 := ip.To16(); ip6 != nil && ip.To4() == nil { switch runtime.GOOS { case "android", "illumos", "linux", "solaris", "windows": - NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6)) + binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6)) default: b[0] = sizeofSockaddrInet6 b[1] = sysAF_INET6 @@ -56,7 +56,7 @@ func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int { binary.BigEndian.PutUint16(b[2:4], uint16(port)) copy(b[8:24], ip6) if zone != "" { - NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone))) + binary.NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone))) } return sizeofSockaddrInet6 } @@ -70,7 +70,7 @@ func parseInetAddr(b []byte, network string) (net.Addr, error) { var af int switch runtime.GOOS { case "android", "illumos", "linux", "solaris", "windows": - af = int(NativeEndian.Uint16(b[:2])) + af = int(binary.NativeEndian.Uint16(b[:2])) default: af = int(b[1]) } @@ -89,7 +89,7 @@ func parseInetAddr(b []byte, network string) (net.Addr, error) { } ip = make(net.IP, net.IPv6len) copy(ip, b[8:24]) - if id := int(NativeEndian.Uint32(b[24:28])); id > 0 { + if id := int(binary.NativeEndian.Uint32(b[24:28])); id > 0 { zone = zoneCache.name(id) } } diff --git a/vendor/golang.org/x/net/ipv4/header.go b/vendor/golang.org/x/net/ipv4/header.go index a00a3eaff91..ee6edae14b9 100644 --- a/vendor/golang.org/x/net/ipv4/header.go +++ b/vendor/golang.org/x/net/ipv4/header.go @@ -9,8 +9,6 @@ import ( "fmt" "net" "runtime" - - "golang.org/x/net/internal/socket" ) const ( @@ -68,12 +66,12 @@ func (h *Header) Marshal() ([]byte, error) { flagsAndFragOff := (h.FragOff & 0x1fff) | int(h.Flags<<13) switch runtime.GOOS { case "darwin", "ios", "dragonfly", "netbsd": - socket.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen)) - socket.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff)) + binary.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen)) + binary.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff)) case "freebsd": if freebsdVersion < 1100000 { - socket.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen)) - socket.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff)) + binary.NativeEndian.PutUint16(b[2:4], uint16(h.TotalLen)) + binary.NativeEndian.PutUint16(b[6:8], uint16(flagsAndFragOff)) } else { binary.BigEndian.PutUint16(b[2:4], uint16(h.TotalLen)) binary.BigEndian.PutUint16(b[6:8], uint16(flagsAndFragOff)) @@ -127,15 +125,15 @@ func (h *Header) Parse(b []byte) error { h.Dst = net.IPv4(b[16], b[17], b[18], b[19]) switch runtime.GOOS { case "darwin", "ios", "dragonfly", "netbsd": - h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + hdrlen - h.FragOff = int(socket.NativeEndian.Uint16(b[6:8])) + h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4])) + hdrlen + h.FragOff = int(binary.NativeEndian.Uint16(b[6:8])) case "freebsd": if freebsdVersion < 1100000 { - h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4])) if freebsdVersion < 1000000 { h.TotalLen += hdrlen } - h.FragOff = int(socket.NativeEndian.Uint16(b[6:8])) + h.FragOff = int(binary.NativeEndian.Uint16(b[6:8])) } else { h.TotalLen = int(binary.BigEndian.Uint16(b[2:4])) h.FragOff = int(binary.BigEndian.Uint16(b[6:8])) diff --git a/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go b/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go index a8f04e7b3b8..e363a3a100c 100644 --- a/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go +++ b/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go @@ -7,6 +7,7 @@ package ipv6 import ( + "encoding/binary" "unsafe" "golang.org/x/net/internal/iana" @@ -19,7 +20,7 @@ func marshal2292HopLimit(b []byte, cm *ControlMessage) []byte { m := socket.ControlMessage(b) m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_2292HOPLIMIT, 4) if cm != nil { - socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) + binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) } return m.Next(4) } diff --git a/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go b/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go index 51fbbb1f170..95259662e34 100644 --- a/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go +++ b/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go @@ -7,6 +7,7 @@ package ipv6 import ( + "encoding/binary" "net" "unsafe" @@ -20,26 +21,26 @@ func marshalTrafficClass(b []byte, cm *ControlMessage) []byte { m := socket.ControlMessage(b) m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_TCLASS, 4) if cm != nil { - socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.TrafficClass)) + binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.TrafficClass)) } return m.Next(4) } func parseTrafficClass(cm *ControlMessage, b []byte) { - cm.TrafficClass = int(socket.NativeEndian.Uint32(b[:4])) + cm.TrafficClass = int(binary.NativeEndian.Uint32(b[:4])) } func marshalHopLimit(b []byte, cm *ControlMessage) []byte { m := socket.ControlMessage(b) m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_HOPLIMIT, 4) if cm != nil { - socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) + binary.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) } return m.Next(4) } func parseHopLimit(cm *ControlMessage, b []byte) { - cm.HopLimit = int(socket.NativeEndian.Uint32(b[:4])) + cm.HopLimit = int(binary.NativeEndian.Uint32(b[:4])) } func marshalPacketInfo(b []byte, cm *ControlMessage) []byte { diff --git a/vendor/golang.org/x/net/publicsuffix/data/children b/vendor/golang.org/x/net/publicsuffix/data/children index 986a246a6c0..1f16913084d 100644 Binary files a/vendor/golang.org/x/net/publicsuffix/data/children and b/vendor/golang.org/x/net/publicsuffix/data/children differ diff --git a/vendor/golang.org/x/net/publicsuffix/data/nodes b/vendor/golang.org/x/net/publicsuffix/data/nodes index 38b8999600c..7ac74a1d4fe 100644 Binary files a/vendor/golang.org/x/net/publicsuffix/data/nodes and b/vendor/golang.org/x/net/publicsuffix/data/nodes differ diff --git a/vendor/golang.org/x/net/publicsuffix/data/text b/vendor/golang.org/x/net/publicsuffix/data/text index b151d97de27..38f06ea9e35 100644 --- a/vendor/golang.org/x/net/publicsuffix/data/text +++ b/vendor/golang.org/x/net/publicsuffix/data/text @@ -1 +1 @@ -bolzano-altoadigevje-og-hornnes3-website-us-west-2bomlocustomer-ocienciabonavstackarasjoketokuyamashikokuchuobondigitaloceanspacesakurastoragextraspace-to-rentalstomakomaibarabonesakuratanishikatakazakindustriesteinkjerepbodynaliasnesoddeno-staginglobodoes-itcouldbeworfarsundiskussionsbereichateblobanazawarszawashtenawsapprunnerdpoliticaarparliamenthickarasuyamasoybookonlineboomladeskierniewiceboschristmasakilovecollegefantasyleaguedagestangebostik-serveronagasukeyword-oncillahppictetcieszynishikatsuragit-repostre-totendofinternet-dnsakurawebredirectmeiwamizawabostonakijinsekikogentlentapisa-geekaratsuginamikatagamimozaporizhzhegurinfinitigooglecode-builder-stg-buildereporthruhereclaimsakyotanabellunord-odalvdalcest-le-patron-k3salangenishikawazukamishihorobotdashgabadaddjabbotthuathienhuebouncemerckmsdscloudisrechtrafficplexus-4boutiquebecologialaichaugianglogowegroweibolognagasakikugawaltervistaikillondonetskarelianceboutireserve-onlineboyfriendoftheinternetflixn--11b4c3ditchyouriparmabozen-sudtirolondrinaplesknsalatrobeneventoeidsvollorenskogloomy-gatewaybozen-suedtirolovableprojectjeldsundivtasvuodnakamai-stagingloppennebplaceditorxn--12c1fe0bradescotaruinternationalovepoparochernihivgubamblebtimnetzjaworznotebook-fips3-fips-us-gov-east-1brandivttasvuotnakamuratajirintlon-2brasiliadboxoslodingenishimerabravendbarcelonagawakuyabukikiraragusabaerobatickets3-fips-us-gov-west-1bresciaogashimadachicappabianiceobridgestonebrindisiciliabroadwaybroke-itvedestrandixn--12cfi8ixb8lovesickarlsoybrokerevistathellebrothermesserlidlplfinancialpusercontentjmaxxxn--12co0c3b4evalleaostargets-itjomeldalucaniabrumunddaluccampobassociatesalon-1brusselsaloonishinomiyashironobryanskiervadsoccerhcloudyclusterbrynebweirbzhitomirumaintenanceclothingdustdatadetectoyouracngovtoystre-slidrettozawacnpyatigorskjakamaiedge-stagingreatercnsapporocntozsdeliverycodebergrayjayleaguesardegnarutoshimatta-varjjatranatalcodespotenzakopanecoffeedbackanagawatsonrendercommunity-prochowicecomockashiharacompanyantaishinomakimobetsulifestylefrakkestadurumisakindlegnicahcesuolohmusashimurayamaizuruhr-uni-bochuminamiechizenisshingucciminamifuranocomparemarkerryhotelsardiniacomputercomsecretrosnubarclays3-me-south-1condoshiibabymilk3conferenceconstructioniyodogawaconsuladobeio-static-accesscamdvrcampaniaconsultantranbyconsultingretakamoriokakudamatsuecontactivetrail-central-1contagematsubaracontractorstabacgiangiangryconvexecute-apictureshinordkappaviacookingrimstadynathomebuiltwithdarklangevagrarchitectestingripeeweeklylotterycooperativano-frankivskjervoyagecoprofesionalchikugodaddyn-o-saureadymadethis-a-anarchistjordalshalsenl-ams-1corsicafederationfabricable-modemoneycosenzamamidorivnecosidnsdojoburgriwataraindroppdalcouchpotatofriesarlcouncilcouponstackitagawacozoracpservernamegataitogodoesntexisteingeekashiwaracqcxn--1lqs71dyndns-at-homedepotrani-andria-barletta-trani-andriacrankyotobetsulubin-dsldyndns-at-workisboringsakershusrcfdyndns-blogsiteleaf-south-1crdyndns-freeboxosarpsborgroks-theatrentin-sud-tirolcreditcardyndns-homednsarufutsunomiyawakasaikaitakokonoecreditunioncremonasharis-a-bulls-fancrewp2cricketnedalcrimeast-kazakhstanangercrispawnextdirectraniandriabarlettatraniandriacrminamiiseharacrotonecrownipfizercrsasayamacruisesaseboknowsitallcryptonomichiharacuisinellamdongnairflowersassaris-a-candidatecuneocuritibackdropalermobarag-cloud-charitydalp1cutegirlfriendyndns-ipgwangjulvikashiwazakizunokuniminamiashigarafedoraprojectransiphdfcbankasserverrankoshigayakagefeirafembetsukubankasukabeautypedreamhosterscrapper-sitefermodalenferraraferraris-a-celticsfanferreroticallynxn--2scrj9cargoboavistanbulsan-sudtiroluhanskarmoyfetsundyndns-remotewdhlx3fgroundhandlingroznyfhvalerfilegear-sg-1filminamiminowafinalfinancefinnoyfirebaseapphilipscrappingrphonefosscryptedyndns-serverdalfirenetgamerscrysecuritytacticscwestus2firenzeaburfirestonefirmdaleilaocairportranslatedyndns-webhareidsbergroks-thisayamanobearalvahkikonaikawachinaganoharamcoachampionshiphoplixn--1qqw23afishingokasellfyresdalfitjarfitnessettsurugashimamurogawafjalerfkasumigaurayasudaflesbergrueflickragerotikagoshimandalflierneflirflogintohmangoldpoint2thisamitsukefloppymntransportefloraclegovcloudappservehttpbincheonflorencefloripadualstackasuyakumoduminamioguni5floristanohatakaharunservehumourfloromskoguidefinimalopolskanittedalfltransurlflutterflowhitesnowflakeflyfncarrdiyfndyndns-wikinkobayashimofusadojin-the-bandairlinemurorangecloudplatformshakotanpachihayaakasakawaharacingrondarfoolfor-ourfor-somedusajserveircasacampinagrandebulsan-suedtirolukowesleyfor-theaterfordebianforexrotheworkpccwhminamisanrikubetsupersaleksvikaszubytemarketingvollforgotdnserveminecraftrapanikkoelnforli-cesena-forlicesenaforlikescandypopensocialforsalesforceforsandasuoloisirservemp3fortalfosneservep2photographysiofotravelersinsurancefoxn--30rr7yfozfr-1fr-par-1fr-par-2franalytics-gatewayfredrikstadyndns-worksauheradyndns-mailfreedesktopazimuthaibinhphuocprapidyndns1freemyiphostyhostinguitarservepicservequakefreesitefreetlservesarcasmilefreightravinhlonganfrenchkisshikirovogradoyfreseniuservicebuskerudynnsaveincloudyndns-office-on-the-webflowtest-iservebloginlinefriuli-v-giuliarafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfrogansevastopolitiendafrognfrolandynservebbsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriafrom-akamaiorigin-stagingujaratmetacentruminamitanefrom-alfrom-arfrom-azureedgecompute-1from-caltanissettainaircraftraeumtgeradealstahaugesunderfrom-cockpitrdynuniversitysvardofrom-ctrentin-sudtirolfrom-dcasertaipeigersundnparsaltdaluroyfrom-decafjsevenassieradzfrom-flatangerfrom-gap-southeast-3from-higashiagatsumagoianiafrom-iafrom-idynv6from-ilfrom-in-vpncashorokanaiefrom-ksewhoswholidayfrom-kyfrom-langsonyatomigrationfrom-mangyshlakamaized-stagingujohanamakinoharafrom-mdynvpnplusavonarviikamisatokonamerikawauefrom-meetrentin-sued-tirolfrom-mihamadanangoguchilloutsystemscloudscalebookinghosteurodirfrom-mnfrom-modellingulenfrom-msexyfrom-mtnfrom-ncasinordeste-idclkarpaczest-a-la-maisondre-landray-dnsaludrayddns-ipartintuitjxn--1ck2e1barclaycards3-globalatinabelementorayomitanobservableusercontentateyamauth-fipstmninomiyakonojosoyrovnoticeableitungsenirasakibxos3-ca-central-180reggio-emilia-romagnaroyolasitebinordlandeus-canvasitebizenakanojogaszkolamericanfamilyds3-ap-south-12hparallelimodxboxeroxjavald-aostaticsxmitakeharaugustow-corp-staticblitzgorzeleccocotteatonamifunebetsuikirkenes3-ap-northeast-2ixn--0trq7p7nninjambylive-oninohekinanporovigonnakasatsunaibigawaukraanghkembuchikumagayagawakkanaibetsubame-central-123websitebuildersvp4from-ndyroyrvikingrongrossetouchijiwadedyn-berlincolnfrom-nefrom-nhlfanfrom-njsheezyfrom-nminamiuonumatsunofrom-nvalled-aostargithubusercontentrentin-suedtirolfrom-nysagamiharafrom-ohdancefrom-okegawafrom-orfrom-palmasfjordenfrom-pratohnoshookuwanakanotoddenfrom-ris-a-chefashionstorebaseljordyndns-picsbssaudafrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--32vp30hachinoheavyfrom-utsiracusagemakerfrom-val-daostavalleyfrom-vtrentino-a-adigefrom-wafrom-wiardwebspaceconfigunmarnardalfrom-wvalledaostarnobrzeguovdageaidnunjargausdalfrom-wyfrosinonefrostalowa-wolawafroyal-commissionfruskydivingushikamifuranorth-kazakhstanfujiiderafujikawaguchikonefujiminokamoenairtelebitbucketrzynh-servebeero-stageiseiroutingthecloudfujinomiyadappnodearthainguyenfujiokazakiryuohkurafujisatoshoeshellfujisawafujishiroishidakabiratoridediboxafujitsuruokakamigaharafujiyoshidatsunanjoetsumidaklakasamatsudogadobeioruntimedicinakaiwanairforcentralus-1fukayabeagleboardfukuchiyamadattorelayfukudomigawafukuis-a-conservativefsnoasakakinokiafukumitsubishigakisarazure-apigeefukuokakegawafukuroishikariwakunigamiharuovatlassian-dev-builderfukusakishiwadattoweberlevagangaviikanonjis-a-cpanelfukuyamagatakahashimamakisofukushimaniwamannordre-landfunabashiriuchinadavvenjargamvikatowicefunagatakahatakaishimokawafunahashikamiamakusatsumasendaisenergyeonggiizefundfunkfeuerfunnelshimonitayanagitapphutholdingsmall-websozais-a-cubicle-slaveroykenfuoiskujukuriyamaoris-a-democratrentino-aadigefuosskodjeezfurubirafurudonordreisa-hockeynutwentertainmentrentino-alto-adigefurukawaiishoppingxn--3bst00minamiyamashirokawanabeepsondriobranconagarahkkeravjunusualpersonfusoctrangyeongnamdinhs-heilbronnoysundfussaikisosakitahatakamatsukawafutabayamaguchinomihachimanagementrentino-altoadigefutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinairtrafficmanagerfuturecmshimonosekikawafuturehosting-clusterfuturemailingzfvghakuis-a-doctoruncontainershimotsukehakusandnessjoenhaldenhalfmoonscaleforcehalsaitamatsukuris-a-financialadvisor-aurdalham-radio-ophuyenhamburghammarfeastasiahamurakamigoris-a-fullstackaufentigerhanamigawahanawahandahandcraftedugit-pages-researchedmarketplacehangglidinghangoutrentino-s-tirolhannannestadhannoshiroomghanoiphxn--3ds443ghanyuzenhappoumuginowaniihamatamakawajimap-southeast-4hasamazoncognitoigawahasaminami-alpshimotsumahashbanghasudahasura-appigboatshinichinanhasvikautokeinotionhatenablogspotrentino-stirolhatenadiaryhatinhachiojiyachiyodazaifudaigojomedio-campidano-mediocampidanomediohatogayachtshinjournalistorfjordhatoyamazakitakatakanezawahatsukaichikawamisatohokkaidontexistmein-iservschulegalleryhattfjelldalhayashimamotobusells-for-lesshinjukuleuvenicehazuminobushibuyahabacninhbinhdinhktrentino-sud-tirolhelpgfoggiahelsinkitakyushunantankazohemneshinkamigotoyokawahemsedalhepforgeblockshinshinotsupplyhetemlbfanheyflowienhigashichichibuzzhigashihiroshimanehigashiizumozakitamihokksundhigashikagawahigashikagurasoedahigashikawakitaaikitamotosumy-routerhigashikurumegurownproviderhigashimatsushimarriottrentino-sudtirolhigashimatsuyamakitaakitadaitomanaustdalhigashimurayamamotorcycleshinshirohigashinarusells-for-uzhhorodhigashinehigashiomitamamurausukitanakagusukumodshintokushimahigashiosakasayamanakakogawahigashishirakawamatakaokalmykiahigashisumiyoshikawaminamiaikitashiobarahigashitsunospamproxyhigashiurawa-mazowszexposeducatorprojectrentino-sued-tirolhigashiyamatokoriyamanashijonawatehigashiyodogawahigashiyoshinogaris-a-geekazunotogawahippythonanywherealminanohiraizumisatokaizukaluganskddiamondshintomikasaharahirakatashinagawahiranais-a-goodyearhirarahiratsukagawahirayahikobeatshinyoshitomiokamisunagawahitachiomiyakehitachiotaketakarazukamaishimodatehitradinghjartdalhjelmelandholyhomegoodshiojirishirifujiedahomeipikehomelinuxn--3e0b707ehomesecuritymacaparecidahomesecuritypcateringebungotakadaptableclerc66116-balsfjordeltaiwanumatajimidsundeportebinatsukigatakahamalvik8s3-ap-northeast-3utilities-12charstadaokagakirunocelotenkawadlugolekadena4ufcfanimsiteasypanelblagrigentobishimafeloansncf-ipfstdlibestadultatarantoyonakagyokutoyonezawapartments3-ap-northeast-123webseiteckidsmynascloudfrontierimo-siemenscaledekaascolipicenoboribetsubsc-paywhirlimitedds3-accesspoint-fips3-ap-east-123miwebaccelastx4432-b-datacenterprisesakihokuizumoarekepnord-aurdalipaynow-dns-dynamic-dnsabruzzombieidskogasawarackmazerbaijan-mayenbaidarmeniajureggio-calabriaknoluoktagajoboji234lima-citychyattorneyagawafflecellclstagehirnayorobninsk123kotisivultrobjectselinogradimo-i-ranamizuhobby-siteaches-yogano-ip-ddnsgeekgalaxyzgierzgorakrehamnfshostrowwlkpnftstorage164-balsan-suedtirolillyokozeastus2000123paginawebadorsiteshikagamiishibechambagricoharugbydgoszczecin-addrammenuorogerscbgdyniaktyubinskaunicommuneustarostwodzislawdev-myqnapcloudflarecn-northwest-123sitewebcamauction-acornikonantotalimanowarudakunexus-2038homesenseeringhomeskleppilottottoris-a-greenhomeunixn--3hcrj9catfoodraydnsalvadorhondahonjyoitakasagonohejis-a-guruzshioyaltakkolobrzegersundongthapmircloudnshome-webservercelliguriahornindalhorsells-itrentino-suedtirolhorteneiheijis-a-hard-workershirahamatonbetsupportrentinoa-adigehospitalhotelwithflightshirakomaganehotmailhoyangerhoylandetakasakitaurahrsnillfjordhungyenhurdalhurumajis-a-hunterhyllestadhyogoris-a-knightpointtokashikitchenhypernodessaitokamachippubetsubetsugaruhyugawarahyundaiwafuneis-uberleetrentinoaltoadigeis-very-badis-very-evillasalleirvikharkovallee-d-aosteis-very-goodis-very-niceis-very-sweetpepperugiais-with-thebandoomdnsiskinkyowariasahikawaisk01isk02jellybeanjenv-arubahcavuotnagahamaroygardenflfanjeonnamsosnowiecaxiaskoyabenoopssejny-1jetztrentinos-tiroljevnakerjewelryjlljls-sto1jls-sto2jls-sto365jmpioneerjnjcloud-ver-jpcatholicurus-3joyentrentinostiroljoyokaichibahccavuotnagaivuotnagaokakyotambabybluebitemasekd1jozis-a-llamashikiwakuratejpmorgangwonjpnjprshoujis-a-musiciankoseis-a-painterhostsolutionshiraokamitsuekosheroykoshimizumakis-a-patsfankoshugheshwiiheyahoooshikamagayaitakashimarshallstatebankhplaystation-cloudsitekosugekotohiradomainsurealtypo3serverkotourakouhokumakogenkounosunnydaykouyamatlabcn-north-1kouzushimatrixn--41akozagawakozakis-a-personaltrainerkozowilliamhillkppspdnsigdalkrasnikahokutokyotangopocznore-og-uvdalkrasnodarkredumbrellapykrelliankristiansandcatsiiitesilklabudhabikinokawabajddarqhachirogatakanabeardubaioiraseekatsushikabedzin-brb-hostingkristiansundkrodsheradkrokstadelvaldaostavangerkropyvnytskyis-a-photographerokuappinkfh-muensterkrymisasaguris-a-playershiftrentinoaadigekumamotoyamatsumaebashimogosenkumanowtvalleedaostekumatorinokumejimatsumotofukekumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-republicanonoichinosekigaharakunitachiaraisaijorpelandkunitomigusukukis-a-rockstarachowicekunneppubtlsimple-urlkuokgroupiwatekurgankurobeebyteappleykurogiminamiawajikis-a-socialistockholmestrandkuroisodegaurakuromatsunais-a-soxfankuronkurotakikawasakis-a-studentalkushirogawakustanais-a-teacherkassyncloudkusupabaseminekutchanelkutnokuzumakis-a-techietis-a-liberalkvafjordkvalsundkvamfamplifyappchizip6kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspectrumisawamjondalenmonza-brianzapposirdalmonza-e-della-brianzaptonsbergmonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenapolicemoriyamatsushigemoriyoshiminamibosoftwarendalenugmormonstermoroyamatsuuramortgagemoscowinbarrel-of-knowledgekey-stagingjerstadigickaracolognemrstudio-prodoyonagoyauthgearapps-1and1moseushimoichikuzenmosjoenmoskenesiskomakis-a-therapistoiamosslupskmpspbaremetalpha-myqnapcloudaccess3-sa-east-1mosviknx-serversicherungmotegirlymoviemovimientoolslzmtrainingmuikamiokameokameyamatotakadamukodairamunakatanemuosattemupixolinodeusercontentrentinosud-tirolmurmanskomatsushimasudamurotorcraftrentinosudtirolmusashinodesakatakayamatsuzakis-an-accountantshiratakahagiangmuseumisconfusedmusicanthoboleslawiecommerce-shopitsitevaksdalmutsuzawamutualmy-vigormy-wanggoupilemyactivedirectorymyaddrangedalmyamazeplaymyasustor-elvdalmycloudnasushiobaramydattolocalcertrentinosued-tirolmydbservermyddnskingmydissentrentinosuedtirolmydnsmolaquilarvikomforbargainstitutemp-dnswatches3-us-east-2mydobissmarterthanyoumydrobofageorgeorgiamydsmushcdn77-securecipescaracalculatorskenmyeffectrentinsud-tirolmyfastly-edgemyfirewalledreplittlestargardmyforumishimatsusakahoginozawaonsennanmokurennebuyshousesimplesitemyfritzmyftpaccessojampanasonichernovtsydneymyhome-servermyjinomykolaivencloud66mymailermymediapchiryukyuragifuchungbukharanzanishinoomotegoismailillehammerfeste-ipartsamegawamynetnamegawamyokohamamatsudamypepizzamypetsokananiimilanoticiassurfastly-terrariuminamiizukaminoyamaxunison-servicesaxomyphotoshibalena-devicesokndalmypiemontemypsxn--42c2d9amyrdbxn--45br5cylmysecuritycamerakermyshopblocksolardalmyshopifymyspreadshopselectrentinsudtirolmytabitordermythic-beastsolundbeckommunalforbundmytis-a-bloggermytuleap-partnersomamyvnchitachinakagawassamukawatarittogitsuldalutskartuzymywirebungoonoplurinacionalpmnpodhalepodlasiellakdnepropetrovskanlandpodzonepohlpoivronpokerpokrovskomonotteroypolkowicepoltavalle-aostavernpolyspacepomorzeszowindowsserveftplatter-appkommuneponpesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-an-actresshishikuis-a-libertarianpordenonepornporsangerporsangugeporsgrunnanpoznanpraxihuanprdprereleaseoullensakerprgmrprimetelprincipenzaprivatelinkyard-cloudletsomnarvikomorotsukaminokawanishiaizubangeprivatizehealthinsuranceprogressivegarsheiyufueliv-dnsoowinepromoliserniapropertysnesopotrentinsued-tirolprotectionprotonetrentinsuedtirolprudentialpruszkowinnersor-odalprvcyprzeworskogpunyukis-an-anarchistoloseyouripinokofuefukihabororoshisogndalpupulawypussycatanzarowiosor-varangerpvhackerpvtrentoyosatoyookaneyamazoepwchitosetogliattipsamnangerpzqotoyohashimotoyakokamimineqponiatowadaqslgbtrevisognequalifioapplatterpl-wawsappspacehostedpicardquangngais-an-artistordalquangninhthuanquangtritonoshonais-an-engineeringquickconnectroandindependent-inquest-a-la-masionquicksytesorfoldquipelementsorocabalestrandabergamochizukijobservablehqldquizzesorreisahayakawakamiichinomiyagithubpreviewskrakowitdkontoguraswinoujscienceswissphinxn--45brj9chonanbunkyonanaoshimaringatlanbibaiduckdnsamparachutinglugsjcbnpparibashkiriasyno-dspjelkavikongsbergsynology-diskstationsynology-dspockongsvingertushungrytuvalle-daostaobaolbia-tempio-olbiatempioolbialowiezaganquangnamasteigenoamishirasatochigiftsrhtrogstadtuxfamilytuyenquangbinhthuantwmailvegasrlvelvetromsohuissier-justiceventurestaurantrustkanieruchomoscientistoripresspydebergvestfoldvestnesrvaomoriguchiharaffleentrycloudflare-ipfsortlandvestre-slidrecreationvestre-totennishiawakuravestvagoyvevelstadvfstreakusercontentroitskoninfernovecorealtorvibo-valentiavibovalentiavideovinhphuchoshichikashukudoyamakeupartysfjordrivelandrobakamaihd-stagingmbhartinnishinoshimattelemarkhangelskaruizawavinnicapitalonevinnytsiavipsinaapplockervirginankokubunjis-byklecznagatorokunohealth-carereformincommbankhakassiavirtual-uservecounterstrikevirtualservervirtualuserveexchangevisakuholeckobierzyceviterboliviajessheimperiavivianvivoryvixn--45q11chowdervlaanderennesoyvladikavkazimierz-dolnyvladimirvlogisticstreamlitapplcube-serversusakis-an-actorvmitourismartlabelingvolvologdanskontumintshowavolyngdalvoorlopervossevangenvotevotingvotoyotap-southeast-5vps-hostreaklinkstrippervusercontentrvaporcloudwiwatsukiyonotairesindevicenzaokinawashirosatochiokinoshimagazinewixsitewixstudio-fipstrynwjgorawkzwloclawekonyvelolipopmcdirwmcloudwmelhustudynamisches-dnsorumisugitomobegetmyipifony-2wmflabstuff-4-salewoodsidell-ogliastrapiapplinzis-certifiedworldworse-thanhphohochiminhadanorthflankatsuyamassa-carrara-massacarraramassabunzenwowithgoogleapiszwpdevcloudwpenginepoweredwphostedmailwpmucdn77-sslingwpmudevelopmentrysiljanewaywpsquaredwritesthisblogoiplumbingotpantheonsitewroclawsglobalacceleratorahimeshimakanegasakievennodebalancernwtcp4wtfastlylbarefootballooningjerdrumemergencyonabarumemorialivornobservereitatsunofficialolitapunkapsienamsskoganeindependent-panelombardiademfakefurniturealestatefarmerseinemrnotebooks-prodeomniwebthings3-object-lambdauthgear-stagingivestbyglandroverhallair-traffic-controllagdenesnaaseinet-freaks3-deprecatedgcagliarissadistgstagempresashibetsukuiitatebayashikaoirmembers3-eu-central-1kapp-ionosegawafaicloudineat-urlive-websitehimejibmdevinapps3-ap-southeast-1337wuozuerichardlillesandefjordwwwithyoutuberspacewzmiuwajimaxn--4it797koobindalxn--4pvxs4allxn--54b7fta0cchromediatechnologyeongbukarumaifmemsetkmaxxn--1ctwolominamatarpitksatmalluxenishiokoppegardrrxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49chungnamdalseidfjordtvsangotsukitahiroshimarcherkasykkylvenneslaskerrypropertiesanjotelulublindesnesannanishitosashimizunaminamidaitolgaularavellinodeobjectsannoheliohostrodawaraxn--5rtq34kooris-a-nascarfanxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264churchaselfipirangallupsunappgafanishiwakinuyamashinazawaxn--80aaa0cvacationstufftoread-booksnesoundcastreak-linkomvuxn--3pxu8khmelnitskiyamassivegridxn--80adxhksurnadalxn--80ao21axn--80aqecdr1axn--80asehdbarrell-of-knowledgesuite-stagingjesdalombardyn-vpndns3-us-gov-east-1xn--80aswgxn--80audnedalnxn--8dbq2axn--8ltr62kopervikhmelnytskyivalleeaostexn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisencoreapiacenzachpomorskiengiangxn--90a3academiamibubbleappspotagerxn--90aeroportsinfolkebibleasingrok-freeddnsfreebox-osascoli-picenogatachikawakayamadridvagsoyerxn--90aishobaraoxn--90amckinseyxn--90azhytomyradweblikes-piedmontuckerxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byameloyxn--asky-iraxn--aurskog-hland-jnbarsycenterprisecloudbeesusercontentattoolforgerockyonagunicloudiscordsays3-us-gov-west-1xn--avery-yuasakuragawaxn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbarsyonlinequipmentaveusercontentawktoyonomurauthordalandroidienbienishiazaiiyamanouchikujolsterehabmereisenishigotembaixadavvesiidaknongivingjemnes3-eu-north-1xn--bck1b9a5dre4ciprianiigatairaumalatvuopmicrosoftbankasaokamikoaniikappudopaaskvollocaltonetlifyinvestmentsanokashibatakatsukiyosembokutamakiyosunndaluxuryxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2hosted-by-previderxn--bjarky-fyanagawaxn--bjddar-ptarumizusawaxn--blt-elabkhaziamallamaceiobbcircleaningmodelscapetownnews-stagingmxn--1lqs03nissandoyxn--bmlo-grafana-developerauniterois-coolblogdnshisuifuettertdasnetzxn--bod-2naturalxn--bozen-sdtirol-2obihirosakikamijimayfirstorjdevcloudjiffyxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagespeedmobilizeropslattumbriaxn--brum-voagatulaspeziaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbasicserver-on-webpaaskimitsubatamicrolightingjovikaragandautoscanaryggeemrappui-productions3-eu-west-1xn--c1avgxn--c2br7gxn--c3s14mitoyoakexn--cck2b3basilicataniavocats3-eu-west-2xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-foundationxn--ciqpnxn--clchc0ea0b2g2a9gcdn77-storagencymrulezajskiptveterinaireadthedocs-hostedogawarabikomaezakishimabarakawagoexn--czr694basketballfinanzlgkpmglassessments3-us-west-1xn--czrs0t0xn--czru2dxn--d1acj3batsfjordiscordsezpisdnipropetrovskygearapparasiteu-2xn--d1alfastvps-serverisignxn--d1atunesquaresinstagingxn--d5qv7z876ciscofreakadns-cloudflareglobalashovhachijoinvilleirfjorduponthewifidelitypeformesswithdnsantamariakexn--davvenjrga-y4axn--djrs72d6uyxn--djty4koryokamikawanehonbetsuwanouchikuhokuryugasakis-a-nursellsyourhomeftpinbrowsersafetymarketshiraois-a-landscaperspectakasugais-a-lawyerxn--dnna-graingerxn--drbak-wuaxn--dyry-iraxn--e1a4cistrondheimeteorappassenger-associationissayokoshibahikariyalibabacloudcsantoandrecifedexperts-comptablesanukinzais-a-bruinsfanissedalvivanovoldaxn--eckvdtc9dxn--efvn9surveysowaxn--efvy88hadselbuzentsujiiexn--ehqz56nxn--elqq16haebaruericssongdalenviknakatombetsumitakagildeskaliszxn--eveni-0qa01gaxn--f6qx53axn--fct429kosaigawaxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbcitadelhichisochimkentmpatriaxn--fiq64bauhauspostman-echofunatoriginstances3-us-west-2xn--fiqs8susonoxn--fiqz9suzakarpattiaaxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbentleyoriikarasjohkamikitayamatsurindependent-review-credentialless-staticblitzw-staticblitzxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grajewolterskluwerxn--frna-woaxn--frya-hraxn--fzc2c9e2citicaravanylvenetogakushimotoganexn--fzys8d69uvgmailxn--g2xx48civilaviationionjukujitawaravennaharimalborkdalxn--gckr3f0fauskedsmokorsetagayaseralingenovaraxn--gecrj9clancasterxn--ggaviika-8ya47hagakhanhhoabinhduongxn--gildeskl-g0axn--givuotna-8yanaizuxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-gonexn--gmqw5axn--gnstigbestellen-zvbentrendhostingleezeu-3xn--gnstigliefern-wobiraxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3evenesuzukanazawaxn--h2brj9c8cldmail-boxfuseljeducationporterxn--h3cuzk1dielddanuorris-into-animein-vigorlicexn--hbmer-xqaxn--hcesuolo-7ya35beppublic-inquiryoshiokanumazuryurihonjouwwebhoptokigawavoues3-eu-west-3xn--hebda8beskidyn-ip24xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-fleeklogesquare7xn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyandexcloudxn--io0a7is-into-carshitaramaxn--j1adpdnsupdaterxn--j1aefbsbxn--2m4a15exn--j1ael8bestbuyshoparenagareyamagentositenrikuzentakataharaholtalengerdalwaysdatabaseballangenkainanaejrietiengiangheannakadomarineen-rootaribeiraogakicks-assnasaarlandiscountry-snowplowiczeladzxn--j1amhagebostadxn--j6w193gxn--jlq480n2rgxn--jlster-byaotsurgeryxn--jrpeland-54axn--jvr189mittwaldserverxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--4dbgdty6choyodobashichinohealthcareersamsclubartowest1-usamsungminakamichikaiseiyoichipsandvikcoromantovalle-d-aostakinouexn--koluokta-7ya57haibarakitakamiizumisanofidonnakaniikawatanaguraxn--kprw13dxn--kpry57dxn--kput3is-into-cartoonshizukuishimojis-a-linux-useranishiaritabashikshacknetlibp2pimientaketomisatourshiranukamitondabayashiogamagoriziaxn--krager-gyasakaiminatoyotomiyazakis-into-gamessinaklodzkochikushinonsenasakuchinotsuchiurakawaxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfirmalselveruminisitexn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasugitlabbvieeexn--kvnangen-k0axn--l-1fairwindsuzukis-an-entertainerxn--l1accentureklamborghinikolaeventsvalbardunloppadoval-d-aosta-valleyxn--laheadju-7yasuokannamimatakatoris-leetrentinoalto-adigexn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52bhzc01xn--lesund-huaxn--lgbbat1ad8jejuxn--lgrd-poacctfcloudflareanycastcgroupowiat-band-campaignoredstonedre-eikerxn--lhppi-xqaxn--linds-pramericanexpresservegame-serverxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liaclerkstagentsaobernardovre-eikerxn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddesvchoseikarugalsacexn--mgb9awbfbx-oschokokekscholarshipschoolbusinessebytomaridagawarmiastapleschoolsztynsetranoyxn--mgba3a3ejtunkonsulatinowruzhgorodxn--mgba3a4f16axn--mgba3a4fra1-dellogliastraderxn--mgba7c0bbn0axn--mgbaam7a8haiduongxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00bialystokkeymachineu-4xn--mgbai9azgqp6jelasticbeanstalkhersonlanxesshizuokamogawaxn--mgbayh7gparaglidingxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexperimentsveioxn--mgbpl2fhskypecoris-localhostcertificationxn--mgbqly7c0a67fbclever-clouderavpagexn--mgbqly7cvafricapooguyxn--mgbt3dhdxn--mgbtf8fldrvareservdxn--mgbtx2bielawalbrzycharternopilawalesundiscourses3-website-ap-northeast-1xn--mgbx4cd0abogadobeaemcloud-ip-dynamica-west-1xn--mix082fbxoschulplattforminamimakis-a-catererxn--mix891fedjeepharmacienschulserverxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44cleverappsaogoncanva-appsaotomelbournexn--mkru45is-lostrolekamakurazakiwielunnerxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-not-axn--mosjen-eyatsukanoyaizuwakamatsubushikusakadogawaxn--mot-tlavangenxn--mre-og-romsdal-qqbuservebolturindalxn--msy-ula0haiphongolffanshimosuwalkis-a-designerxn--mtta-vrjjat-k7aflakstadotsurugimbiella-speziaxarnetbankanzakiyosatokorozawaustevollpagest-mon-blogueurovision-ranchernigovernmentdllivingitpagemprendeatnuh-ohtawaramotoineppueblockbusterniizaustrheimdbambinagisobetsucks3-ap-southeast-2xn--muost-0qaxn--mxtq1miuraxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4dbrk0cexn--nit225kosakaerodromegalloabatobamaceratabusebastopoleangaviikafjordxn--nmesjevuemie-tcbalsan-sudtirolkuszczytnord-fron-riopretodayxn--nnx388axn--nodeloittexn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsicilyxn--o3cw4hair-surveillancexn--o3cyx2axn--od0algardxn--od0aq3bielskoczoweddinglitcheap-south-2xn--ogbpf8flekkefjordxn--oppegrd-ixaxn--ostery-fyatsushiroxn--osyro-wuaxn--otu796dxn--p1acfolksvelvikonskowolayangroupippugliaxn--p1ais-not-certifiedxn--pgbs0dhakatanortonkotsumomodenakatsugawaxn--porsgu-sta26fedorainfracloudfunctionschwarzgwesteuropencraftransfer-webappharmacyou2-localplayerxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4clickrisinglesjaguarvodkagaminombrendlyngenebakkeshibukawakeliwebhostingouv0xn--qcka1pmcprequalifymeinforumzxn--qqqt11miyazure-mobilevangerxn--qxa6axn--qxamiyotamanoxn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-savedxn--rennesy-v1axn--rhkkervju-01afedorapeopleikangerxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturbruksgymnxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byawaraxn--rny31hakodatexn--rovu88bieszczadygeyachimataijinderoyusuharazurefdietateshinanomachintaifun-dnsaliases121xn--rros-granvindafjordxn--rskog-uuaxn--rst-0navigationxn--rsta-framercanvasvn-repospeedpartnerxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawatahamaxn--s-1faitheshopwarezzoxn--s9brj9clientoyotsukaidownloadurbanamexnetfylkesbiblackbaudcdn-edgestackhero-networkinggroupperxn--sandnessjen-ogbizxn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphicswidnicaobangxn--skierv-utazurecontainerimamateramombetsupplieswidnikitagatamayukuhashimokitayamaxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navoizumizakis-slickharkivallee-aosteroyxn--slt-elabievathletajimabaria-vungtaudiopsys3-website-ap-southeast-1xn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbifukagawalmartaxiijimarugame-hostrowieconomiasagaeroclubmedecin-berlindasdaeguambulancechireadmyblogsytecnologiazurestaticappspaceusercontentproxy9guacuiababia-goraclecloudappschaefflereggiocalabriaurland-4-salernooreggioemiliaromagnarusawaurskog-holandinggff5xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbigv-infolldalomoldegreeu-central-2xn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bookkeepermashikexn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbiharvanedgeappengineu-south-1xn--stre-toten-zcbihoronobeokayamagasakikuchikuseihicampinashikiminohostfoldiscoverbaniazurewebsitests3-external-1xn--t60b56axn--tckwebview-assetswiebodzindependent-commissionxn--tiq49xqyjelenia-goraxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbikedaejeonbuk0emmafann-arborlandd-dnsfor-better-thanhhoarairkitapps-audiblebesbyencowayokosukanraetnaamesjevuemielnogiehtavuoatnabudejjuniper2-ddnss3-123minsidaarborteamsterdamnserverseating-organicbcg123homepagexl-o-g-i-navyokote123hjemmesidealerdalaheadjuegoshikibichuo0o0g0xn--trentin-sdtirol-7vbiomutazas3-website-ap-southeast-2xn--trentino-sd-tirol-c3birkenesoddtangentapps3-website-eu-west-1xn--trentino-sdtirol-szbittermezproxyusuitatamotors3-website-sa-east-1xn--trentinosd-tirol-rzbjarkoyuullensvanguardisharparisor-fronishiharaxn--trentinosdtirol-7vbjerkreimmobilieniwaizumiotsukumiyamazonaws-cloud9xn--trentinsd-tirol-6vbjugnieznorddalomzaporizhzhiaxn--trentinsdtirol-nsblackfridaynightayninhaccalvinklein-butterepairbusanagochigasakindigenakayamarumorimachidaxn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvarggatromsakegawaxn--uc0ay4axn--uist22hakonexn--uisz3gxn--unjrga-rtashkenturystykanmakiyokawaraxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtuscanyxn--valle-d-aoste-ehboehringerikerxn--valleaoste-e7axn--valledaoste-ebbvaapstempurlxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbloombergentingliwiceu-south-2xn--vestvgy-ixa6oxn--vg-yiablushangrilaakesvuemieleccevervaultgoryuzawaxn--vgan-qoaxn--vgsy-qoa0j0xn--vgu402clinicarbonia-iglesias-carboniaiglesiascarboniaxn--vhquvaroyxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bmoattachments3-website-us-east-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1cliniquenoharaxn--wgbl6axn--xhq521bms3-website-us-gov-west-1xn--xkc2al3hye2axn--xkc2dl3a5ee0hakubaclieu-1xn--y9a3aquarelleborkangerxn--yer-znavuotnarashinoharaxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4gbriminiserverxn--ystre-slidre-ujbmwcloudnonproddaemongolianishiizunazukindustriaxn--zbx025dxn--zf0avxn--4it168dxn--zfr164bnrweatherchannelsdvrdns3-website-us-west-1xnbayernxz \ No newline at end of file +bonagasukeymachinebondigitaloceanspaces3-website-us-west-1bones3-website-us-west-2boomla1-plenitvedestrandiskstationcillair-traffic-controllagdenesnaaseinet-freaksakurastorageboschristmasakikuchikuseihicampinashikiminohostfoldiskussionsbereicheap-east-2bostik-serverrankoshigayachiyodaklakasamatsudoes-itjmaxxxn--12c1fe0brandisrechtrainingkpmgdbarclays3-fips-us-gov-west-1bostonakijinsekikogentlentapisa-geekarlsoyoriikarmoyoshiokanravoues3-eu-west-3botdashgabadaddjabbottjomelhus-northeast-1bouncemerckmsdsclouditchyouriparsakuratanishiwakinderoyurihonjournalistreaklinksakurawebredirectmelbourneboutiquebecologialaichaugianglassessmentsakyotanabellunoorepairbusanagochigasakishimabarakawagoeboutireserve-onlineboyfriendoftheinternetflixn--12cfi8ixb8lorenskogleezebozen-sudtirolovableprojectjxn--12co0c3b4evalleaostamayukuhashimokitayamaxarnetbankanzakiyosatokorozawap-southeast-7bozen-suedtirolovepopartindevsalangenissandoyusuharazurefdienbienishikatakayamatsushigemrstudio-prodoyolasitequipmentateshinanomachintaifun-dnshome-webservercellillesandefjordietateyamapartments3-ca-central-1bplacedogawarabikomaezakirunord-frontierepbodynathomebuiltwithdarklangevagrarmeniazurestaticappspaceusercontentproxy9guacuedaeguambulancechireadmyblogoip-dynamica-west-180recipescaracalculatorskeninjambylimanowarudaetnaamesjevuemielnogatabuseating-organicbcg123homepagexlimitedeltaitogliattips3-ap-northeast-3utilitiesmall-websozaibetsubamericanfamilydstcgroupperimo-siemenscaledekadena4ufcfaninohekinanporovnospamproxyokoteatonamidsundeportebetsukubank123kotisivultrobjectselinogradimo-i-ranamizuhobby-siteaches-yogano-ip-ddnsgurugbydgoszczecin-addrammenuorogerscblackbaudcdn-edgestackhero-networkinggroupowiat-band-campaignieznoboribetsubsc-paywhirlimodumemergencymruovatlassian-dev-buildereclaims3-ap-south-12hparasiteasypanelblagrigentobamaceiobbcn-north-123websitebuildersvp4lima-citychyattorneyagawafaicloudinedre-eiker2-deloitteastus2000123webseiteckidsmynascloudfrontendofinternet-dnsnasaarlandds3-ap-northeast-123sitewebcamauction-acornimsite164-balsan-suedtirolillyokosukanoyakage2balsfjorddnss3-accesspoint-fips3-ap-east-123paginawebadorsiteshikagamiishibechambagrice-labss3-123minsidaarborteamsterdamnserverbaniamallamazonwebservices-123miwebaccelastx4432-b-datacenterprisesakievennodebalancernfshostrowwlkpnftstorage123hjemmeside5brasiliadboxosascoli-picenord-odalovesickarpaczest-a-la-maisondre-landivtasvuodnakamurataiwanumatajimidorivnebravendbarefootballangenovarahkkeravjuh-ohtawaramotoineppueblockbusternikkoelnishikatsuragit-repostre-toteneiheijiitatebayashikaoizumizakitchenishikawazukamisatokonamerikawaueu-2bresciaogashimadachicappadovaapstecnologiazurewebsitests3-external-1bridgestonebrindisicilynxn--1ck2e1baremetalvdalipaynow-dnsdojobservablehqhaccaltanissettaikikugawaltervistablogivestbyglandroverhallaakesvuemielecceu-3broadwayusuitarumizusawabroke-itkmaxxn--1ctwolominamatargithubpreviewskrakowebview-assetsalatrobeneventochiokinoshimagentositempurlplfinancialpusercontentksatmalluccalvinklein-brb-hostingliwicebrokereportmpartsalon-1brothercules-developerauniteroirmeteorappartypo3serverevistathellebrumunddaluhanskartuzyuullensvanguardivttasvuotnakaniikawatanagurabrusselsaloonissayokoshibahikariyalibabacloudcsaltdalukoweddinglobodontexisteingeekaruizawabryanskierniewicebrynebwcloud-os-instancesaludixn--1lqs03nissedaluroyuzawabzhitomirhcloudiyclientozsdegreeclinicapitalonecliniquenoharaclothingdustdatadetectranbycngouv0cnpyatigorskiptveterinaireadymadethis-a-anarchistjordalshalsencntrani-andria-barletta-trani-andriacodespotenzagancoffeedbackanagawarszawashtenawsapprunnerdpoliticaarpharmaciensanjosoyrocommunity-prochowicecomochizukillvivanovoldacompanyantagonistockholmestrandurumisakimobetsumidanangodoesntexistmein-iservschulegallerycomparemarkerryhotelsannancomputercomsecretrosnubargainsureadthedocs-hosteditorxn--0trq7p7nnishimeraugustow-corp-staticblitzgierzgoraktyubinskaunicommuneencoreapiacenzabc01kapp-ionosegawadlugolekaascolipicenocelotennishiawakuracingheannakadomarineat-urlive-oninomiyakonojorpelandeus-canvasitebinatsukigatajiri234condoshiibabybluebitemasekd1conferenceconstruction-vaporcloudplatformshangriladeskjakamaiedge-stagingreaterconsuladobeio-static-accesscamdvrcampaniaconsultantraniandriabarlettatraniandriaconsultingrebedocapooguycontactivetrailwaycontagematsubaracontractorstababymilkashiwaraconvexecute-apictetcieszyncookingretakahatakaishimokawacooperativano-frankivskjervoyagecoprofesionalchikugodaddyn-o-saurealestatefarmerseinecorsicable-modemoneycosenzakopanecosidnsiskinkyowariasahikawasmercouchpotatofriesannoheliohostrodawaracouncil-central-1couponstackitagawassamukawatarikuzentakatairacozoracpservernamegataishinomakiloappsanokashiwazakiyosellsyourhomeftpharmacyonabaruminamiizukaminokawanishiaizubangecqldyndns-at-homedepotaruiocrankycrdyndns-at-workisboringsakershus-central-1creditcardyndns-blogsytecreditunion-webpaaskoyabenogiftsantamariakecremonasharissadistoloseyouriphdfcbankasserversembokutamakiyosunndalcrewp2cricketnedalcrimeast-kazakhstanangercrispmanagercrminamimakinfinitigooglecodebergrimstadyndns-freeboxosloisirsantoandrealtysnesanukinternationalcrotonecrowniphilipsaobernardovre-eikercrsaogoncanthoboleslawiecommerce-shopitsitecruisesaotomeldalcryptonomichiharacuiabacgiangiangrycuisinellahppictureshinordeste-idclkasukabeatsardegnarvikasumigaurayasudacuneocuritibackdropalermoarekembuchikumagayagawakkanaikawachinaganoharamcoacharitydalaheadjuegoshikibichuocutegirlfriendyndns-homednsardiniafedoraproject-studynaliasnesoddeno-stagingroks-thisayamanobearalvahkijoburgrayjayleagueschokokekscholarshipschoolbusinessebytomaridagawalmartransiphotographysiofeirafembetsukuintuitranslatefermockaszubytemarketingvollferraraferrarinuyamashinazawaferreroticahcesuolohmusashimurayamaizurunschuldockatowicefetsundyndns-remotewdyndns-iphonefossarlfgrongrossetouchijiwadediboxn--2m4a15efhvalerfilegear-sg-1filminamioguni5finalfinancefinnoyfirebaseapplinzinvestmentschulplattforminamisanrikubetsupersalevangerfirenetlibp2phutholdingsmartlabelingroundhandlingroznysaikisosakitahatakamatsukawafirenzefirestonefirmdaleilaocairtelebitbucketrzynh-servebeero-stageiseiroutingthecloudyndns-serverisignfishingokaseljeephuyenfitjarfitnessettsurugiminamitanefjalerflesbergrphxn--2scrj9caravanylvenetoeidsvollutrausercontentoyotsukaidownloadnpassenger-associationl-ams-1flickragerotikagaminordlandyndns-webhareidsbergriwataraindropikeflierneflirflogintohmalopolskanitransportefloppymntransurlfloraclegovcloudappschulserverflorencefloripadualstackatsushikabeautypedreamhosterschwarzgwesleyfloristanohatakahamalselveruminamiuonumatrixn--30rr7yflororoscrapper-sitefltrapanikolaeventscrappingrueflutterflowest1-us1-plenitravelersinsuranceflyfncarbonia-iglesias-carboniaiglesiascarboniafndyndns-wikindlegnicagliaricoharulezajskierval-d-aosta-valleyfoolfor-ourfor-somedusajscryptedyndns-worksarufutsunomiyawakasaikaitakokamikoaniikappudopaaskvolloanswatchesasayamattelemarkhangelskasuyakumodsasebofagefor-theaterfordeatnuniversitysvardoforexrotheshopwarezzoforgotdnscrysecuritytacticscwesteuropencraftravinhlonganforli-cesena-forlicesenaforlifestyleirfjordyndns1forsalesforceforsandasuolojcloud-ver-jpcargoboavistanbulsan-sudtirolutskarumaifminamifuranofortalfosneservehttpbincheonfotrdynnsassarintlon-2foxn--32vp30hachinoheavyfozfr-par-1fr-par-2franalytics-gatewayfredrikstadynservebbsaudafreedesktopazimuthaibinhphuocprapidynuddnsfreebox-osauheradyndns-mailovecollegefantasyleaguefreemyiphostyhostinguidedyn-berlincolnfreesitefreetlservehumourfreightrentin-sudtirolfrenchkisshikirkeneserveircarrdrayddns-ipatriafresenius-central-2friuli-v-giuliarafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganserveminecraftrentin-sued-tirolfrognfrolandynuhosting-clusterfrom-akamaiorigin-staginguitarservemp3from-alfrom-arfrom-azureedgekey-stagingujaratmetacentrumbriafrom-callyfrom-cockpitrentin-suedtirolfrom-ctrentino-a-adigefrom-dcasacampinagrandebulsan-suedtiroluxenonconnectoyourafrom-debianfrom-flatangerfrom-gamvikatsuyamashikizunokuniminamiashigarafrom-hidnservep2pimientakazakinzais-a-bruinsfanfrom-iafrom-idynv6from-ilfrom-in-the-bandairtrafficplexus-2from-kservepicservequakefrom-kyfrom-lamericanexpresseljordyroyrvikingroceryfrom-malvikaufentigerfrom-mdfrom-meetrentino-aadigefrom-mifunefrom-mnfrom-modalenfrom-mservesarcasmolaquilarvikautokeinotionfrom-mtlservicebuskerudfrom-ncasertainairflowersalvadorfrom-ndfrom-nefrom-nhlfanfrom-njsevastopolitiendafrom-nminamiyamashirokawanabeepsongdalenviknagaraholtaleniwaizumiotsurugashimagazinefrom-nvalled-aostaobaolbia-tempio-olbiatempioolbialowiezachpomorskiengiangujohanamakinoharafrom-nyatomigrationidfrom-ohdancefrom-okegawatsonionjukujitawarafrom-orfrom-palmasfjordenfrom-praxihuanfrom-ris-a-bulls-fanfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--3bst00minanofrom-utsiracusagamiharafrom-val-daostavalleyfrom-vtrentino-alto-adigefrom-wafrom-wiardwebspace-hostorachampionshiptodayfrom-wvalledaostargetrentino-altoadigefrom-wyfrosinonefrostalowa-wolawafroyal-commissionporterfruskydivingulenfujiiderafujikawaguchikonefujiminokamoenais-a-candidatefujinomiyadatsunanjoetsulublindesnesevenassieradzfujiokazakirovogradoyfujisatoshoesewestus2fujisawafujishiroishidakabiratoridecafederation-ranchernigovallee-aosteroyfujitsuruokagoshimamurogawafujiyoshidattorelayfukayabeagleboardfukuchiyamadattoweberlevagangaviikanonjis-a-catererfukudomigawafukuis-a-celticsfanfukumitsubishigakiryuohkurafukuokakamigaharafukuroishikariwakunigamihamadavvenjargalsacefukusakisarazure-apigeefukuyamagatakaharunjargaularavellinodeobjectstoragefunabashiriuchinadavvesiidaknongunmaoris-a-chefarsundyndns-office-on-the-webflowtest-iservebloginlinefunagatakahashimamakishiwadazaifudaigoguovdageaidnunusualpersonfunahashikamiamakusatsumasendaisenergyeonggildeskaliszfundfunkfeuerfunnelsexyfuoiskujukuriyamandalfuosskodjeezfurubirafurudonordre-landfurukawaiishoppingushikamifuranore-og-uvdalfusodegaurafussagemakerfutabayamaguchinomihachimanagementrentino-s-tirolfutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinais-a-conservativefsnoasakakinokiafuturecmsheezyfuturehostingxn--3ds443gzfuturemailingfvghakonehakubaclieu-1hakuis-a-cpaneliv-dnshimosuwalkis-a-cubicle-slaveroykenhakusandnessjoenhaldenhalfmoonscaleforcehalsaitamatsukuris-a-democratrentino-stirolham-radio-opocznortonkotsumomodelscapetownnews-staginghamburghammarfeastasiahamurakamigoris-a-designerhanamigawahanawahandahandcraftedugit-pages-researchedmarketplacehangglidinghangoutrentino-sud-tirolhannannestadhannoshiroomghanoipinbrowsersafetymarketshimotsukehanyuzenhappoumuginowaniihamatamakawajimangolffanshimotsumayfirstreamlitappinkddiamondshinichinanhasamazoncognito-idpdnshinjotelulucaniahasaminami-alpshinjukuleuvenicehashbanghasudahasura-appinokofuefukihaborovigoldpoint2thisamitsukehasvikfh-muensterhatenablogisticsxn--3e0b707ehatenadiaryhatinhachiojiyachtshellhatogayahabacninhbinhdinhktrentino-sudtirolhatoyamazakitakamiizumisanofidongthapmircloudnsupdaterhatsukaichikawamisatohokkaidonnakanotoddenhattfjelldalhayashimamotobusellfyis-a-doctoruncontainershinkamigotourshinshinotsupplyhazuminobushibuyahikobearblogsiteleaf-south-1helpgfoggiahelsinkitakatakanabeardubaioirasebastopoleapcellclstagehirnhemneshinshirohemsedalhepforgeblockshintokushimaheroyhetemlbfanheyflowhoswholidayhigashiagatsumagoianiahigashichichibuzentsujiiehigashihiroshimanehigashiizumozakitakyushunantankhakassiahigashikagawahigashikagurasoedahigashikawakitaaikitamiharunzenhigashikurumegurownproviderhigashimatsushimarcherkasykkylvenneslaskerrypropertieshintomikasaharahigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshinyoshitomiokamishihorohigashinarusells-for-lesshiojirishirifujiedahigashinehigashiomitamamurausukitamotosumy-routerhigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitanakagusukumodenaklodzkobierzycehigashitsunotairesindevicenzamamihokksundhigashiurawa-mazowszexposeducationhercules-appioneerhigashiyamatokoriyamanashijonawatehigashiyodogawahigashiyoshinogaris-a-financialadvisor-aurdalhiphoplixn--3hcrj9cashorokanaiehippythonanywherealtorhiraizumisatokaizukakudamatsuehirakatashinagawahiranais-a-fullstackharkivallee-d-aostehirarahiratsukagawahirayahoooshikamagayaitakaokalmykiahitachiomiyakehitachiotaketakarazukaluganskharkovalleeaostehitradinghjartdalhjelmelandholyhomegoodshioyaltaketomisatoyakokonoehomeipippugliahomelinuxn--3pxu8khersonyhomesecuritymacaparecidahomesecuritypccwuozuerichardliguriahomesenseeringhomeskleppivohostinghomeunixn--41ahondahonjyoitakasagonohejis-a-geekhmelnitskiyamashikokuchuohornindalhorsells-for-usgovcloudapilottotalhortenkawahospitalhotelwithflightshirahamatonbetsupportrentino-sued-tirolhotmailhoyangerhoylandetakasakitashiobarahrsnillfjordhungyenhurdalhurumajis-a-goodyearhyllestadhyogoris-a-greenhypernodessaitokamachippubetsuikitaurahyugawarahyundaiwafuneis-not-certifiedis-savedis-slickhplayitrentinos-tirolis-uberleetrentinostirolis-very-badis-very-evillasalleitungsenis-very-goodis-very-niceis-very-sweetpepperugiais-with-thebandoomdnshisuifuettertdasnetzisk01isk02jenv-arubahcavuotnagahamaroygardengerdalp1jeonnamsosnowiecateringebumbleshrimperiajetztrentinosud-tiroljevnakerjewelryjlljls-sto1jls-sto2jls-sto365jmpiwatejnjdfirmalborkdaljouwwebhoptokigawajoyokaichibahccavuotnagaivuotnagaokakyotambabia-goraclecloudappssejny-2jozis-a-knightpointtokashikiwakuratejpmorgangwonjpncatfoodrivelandrobakamaihd-stagingloomy-gatewayjprshitaramakoseis-a-libertariankosherokuappizzakoshimizumakis-a-linux-useranishiaritabashikshacknetlifylkesbiblackfridaynightrentino-suedtirolkoshugheshizuokamitsuekosugekotohiradomainshoujis-a-llamarugame-hostrowieconomiasadogadobeioruntimedicinakanojogaszkolamdongnairlineedleasingkotourakouhokumakogenkounosunnydaykouyamassa-carrara-massacarraramassabuzzkouzushimassivegridkozagawakozakis-a-musiciankozowienkppspbarsycenterprisecloudbeesusercontentaveusercontentawktoyonakagyokutoyonezawauiusercontentdllive-websitebizenakasatsunairportashkentatamotors3-deprecatedgcaffeinehimejibxos3-eu-central-1krasnikahokutokyotangopensocialkrasnodarkredumbrellapykrelliankristiansandcatshowakristiansundkrodsheradkrokstadelvaldaostaticsigdalkropyvnytskyis-a-nascarfankrymisasaguris-a-nursells-itrentinoa-adigekumamotoyamasudakumanowtvaomoriguchiharag-cloud-charternopilawakayamafeloabatochigiehtavuoatnabudejjurkumatorinokumejimatlabgkumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-painterhostsolutionshiranukamisunagawakunitachiaraisaijolsterkunitomigusukukis-a-patsfankunneppubtlsiiitesilknx-serversicherungkuokgroupkomatsushimasoykurgankurobeebyteappenginekurogiminamiawajikis-a-personaltrainerkuroisoftwarendalenugkuromatsunais-a-photographermesserlikescandypoppdalkuronkurotakikawasakis-a-playershiftrentinoaadigekushirogawakustanais-a-republicanonoichinosekigaharakusupabaseoullensakerkutchanelkutnokuzumakis-a-rockstarachowicekvafjordkvalsundkvamfamplifyappchizipifony-1kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsimple-urlmktgorymmvareservdmoliserniamombetsuppliesimplesitemonza-brianzapposirdalmonza-e-della-brianzaptomobegetmyipirangallocustomer-ocienciamonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenarashinoharamoriyamatsumotofukemoriyoshiminamibosogndalmormonstermoroyamatsunomortgagemoscowiiheyaizuwakamatsubushikusakadogawamoseushimoichikuzenmosjoenmoskenesiskomaganemosslingmotegirlymoviemovimientonsbergmtnmtranaritakurashikis-a-socialistordalmuikaminoyamaxunison-serviceslupskomforbarrell-of-knowledgeu-central-2mukodairamunakatanemuosattemupl-wawsappspacehostedpicardmurmanskommunalforbundmurotorcraftrentinosued-tirolmusashinodesakatakatsukis-a-soxfanmuseumisawamusicampobassociateslzmutsuzawamutualmyactivedirectorymyaddrangedalmyamazeplaystation-cloudyclustersmushcdn77-sslgbtrentinosuedtirolmyasustor-elvdalmycloudnasushiobaramydattolocalcertificationmydbservermyddnskingmydissentrentinsud-tirolmydnsokamogawamydobissmarterthanyousrcfdmydsokndalmyeffectrentinsudtirolmyfastly-edgemyfirewalledreplittlestargardmyforumisconfusedmyfritzmyftpaccessolardalmyhome-servermyjinomykolaivencloud66mymailermymediapcatholicp1mynetnamegawamyokohamamatsudamypeplatter-applcube-serversusakis-a-studentalmypetsolundbeckommunemyphotoshibalena-devicesomamypigboatsomnaturalmypsxn--45br5cylmyrdbxn--45brj9caxiaskimitsubatamicrolightingloppennemysecuritycamerakermyshopblocksoowilliamhillmyshopifymyspreadshopselectrentinsued-tirolmysynologyeongnamdinhs-heilbronnoysundmytabitordermythic-beastsopotrentinsuedtirolmytis-a-bloggermytuleap-partnersor-odalmyvnchernovtsydneymywiredbladehostingpodhalepodlasiellakdnepropetrovskanlandpodzonepohlpoivronpokerpokrovskomonotteroypolkowicepoltavalle-aostavangerpolyspacepomorzeszowinbarsyonlinexus-3ponpesaro-urbino-pesarourbinopesaromasvuotnarusawapordenonepornporsangerporsangugeporsgrunnanpoznanprdprereleaserveftplockerprgmrprimeteleportrentoyookanazawaprincipenzaprivatelinkyard-cloudletsor-varangerprivatizehealthinsuranceprogressivegarsheiyufueliv-apiemontepromoldefinimaringatlangsondriobranconakamai-stagingpropertysfjordprotectionprotonettrevisohuissier-justiceprudentialpruszkowindowsservegame-serverprvcyou2-localtonetroandindependent-inquest-a-la-masionprvwineprzeworskogpunyukis-a-teacherkassyncloudpupulawypussycatanzarowinnersorfoldpvhachirogatakamoriokakegawapvtrogstadpwchiryukyuragifuchungbukharavennakaiwanairforceopzqotoyohashimotottoris-a-techietis-a-gurusgovcloudappnodeartheworkpcasinorddaluxuryqponiatowadaqsldqualifioapplumbingotembaixadaqualyhqpartnerqualyhqportalquangngais-a-therapistoiaquangninhthuanquangtritonoshonais-an-accountantshiraois-a-hard-workershirakolobrzegersundojin-dslattuminisitequickconnectroitskomorotsukamiminequicksytesorocabalestrandabergamobaragusabaerobaticketsorreisahayakawakamiichinomiyagitbookinghosteurovisionrenderquipelementsortlandquizzesorumishimatsumaebashimogosenqzzventurestaurantulaspeziavestfoldvestnesquaresinstagingvestre-slidrecifedexperts-comptablesrhtrustkaneyamazoevestre-totenris-an-anarchistorfjordvestvagoyvevelstadvfsrlvibo-valentiavibovalentiavideovinhphuchonanbungotakadaptableclercaobanglogowegroweiboliviajessheimmobilienisshingucciminamiechizeniyodogawavinnicanva-hosted-embedzin-buttervinnytsiavipsinaapplurinacionalvirginankokubunjis-an-artistorjdevcloudjiffyresdalvirtual-uservecounterstrikevirtualservervirtualuserveexchangevisakuholeckochikushinonsenasakuchinotsuchiurakawaviterboknowsitallvivianvivoryvixn--4dbgdty6choseikarugallupfizervkis-an-engineeringvlaanderenvladikavkazimierz-dolnyvladimirennesoyvlogvmitoyoakevolvologdanskonskowolayangroupixolinodeusercontentrentinosudtirolvolyngdalvoorlopervossevangenvotevotingvotoyosatoyonovpnplus-west-3vps-hostrynvusercontentunespritesoundcastripperwithgoogleapiszwithyoutubentrendhostingwiwatsukiyonotebook-fipstuff-4-salewixsitewixstudio-fipstufftoread-booksnesowawjgorawkzwloclawekonsulatinowruzhgorodwmcloudwmeloywmflabsurveyspectrumisugitolgap-north-1wnextdirectwpdevcloudwoodsideliveryworldworse-thanhphohochiminhackerwowiosrvrlessourcecraftromsakegawawpenginepoweredwphostedmailwpmucdn77-storagencywpmudevinappsusonowpsquaredwroclawsglobalacceleratorahimeshimagine-proxywtcp4wtfastly-terrariuminamiminowawwwitdkontogurawzmiuwajimaxn--54b7fta0cchoshichikashukudoyamalatvuopmicrosoftbankasaokamikitayamatsurindigenamsskoganeindustriaxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49chowderxn--5rtq34konyvelolipopmckinseyxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264choyodobashichinohealthcareersame-previeweirxn--80aaa0cvacationsuzakarpattiaaxn--80adxhksuzukananiimilanoticiassurgerydxn--80ao21axn--80aqecdr1axn--80asehdbasicserver-on-k3s3-me-south-1xn--80aswgxn--80audiopsysuzukis-an-actorxn--8dbq2axn--8ltr62koobindalxn--8pvr4uzhhorodxn--8y0a063axn--90a1affinitylotterybnikeeneticp0xn--90a3academiamibubbleappspotagerxn--90aeroportsinfolkebibleangaviikafjordpabianicentralus-1xn--90aishobaraoxn--90amcprequalifymeiwamizawaxn--90azhytomyradweblikes-piedmontunkoninfernovecorespeedpartnerxn--9dbq2axn--9et52uzsprytromsojampanasonichitachinakagawarmiastaplesame-appaviaxn--9krt00axn--9tfkyxn--andy-iraxn--aroport-byamembersvalbarduponthewifidelitypeformitourismilexn--asky-iraxn--aurskog-hland-jnbasilicataniaukraanghkeisenebakkeshibukawakeliwebhostingdyniakunemurorangecloudscalebookonlineustarostwodzislawdev-myqnapcloudflarecn-northwest-1xn--avery-yuasakuragawaxn--b-5gausdalxn--b4w605ferdxn--balsan-sdtirol-nsbasketballfinanzjaworznoticeableksvikapsiciliaurland-4-salernombrendlyngenflfanpachihayaakasakawaharaffleentrycloudflare-ipfstgstageorgeorgiap-southeast-4xn--bck1b9a5dre4chrome-central-1xn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2hosted-by-previderxn--bjddar-ptarnobrzegxn--blt-elabkhaziaxn--bmlo-grafana-developmentunnelmolexn--bod-2naturbruksgymnxn--bozen-sdtirol-2obihirosakikamijimatsuzakis-an-entertainerxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagespeedmobilizeropschaefflerxn--brum-voagaturindalxn--btsfjord-9zaxn--bulsan-sdtirol-nsbatsfjordigickaracologneu-south-1xn--c1avgxn--c2br7gxn--c3s14mittwaldserverxn--cck2b3bauhauspostman-echofunatoriginstitutemp-dns3-object-lambda-urlolitapunkaragandaurskog-holandinggff5xn--cckwcxetdxn--cesena-forl-mcbnpparibashkiriaxn--cesenaforl-i8axn--cg4bkis-byklecznagatoromskoguchilloutsystemscloudsitevaksdalxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--czr694beppublic-inquiryonagoyaustevollivingitlabbvieeemfakefurniturealtimedio-campidano-mediocampidanomediobninsk8s3-eu-north-1xn--czrs0t0xn--czru2dxn--d1acj3beskidyn-ip24xn--d1alfastlylbarrel-of-knowledgesuite-stagingivingjemnes3-globalatinabelementorayomitanobservereggio-emilia-romagnarutoolsztynsetatsunofficialivornomniwebspaceconfigma-governmentattoolforgeu-4xn--d1aturystykanieruchomoscientistreakusercontentrvarggatrysiljanewayxn--d5qv7z876chungnamdalseidfjordrrppgwangjulvikashibatakatorindustriesteinkjerxn--davvenjrga-y4axn--djrs72d6uyxn--djty4kooris-a-lawyerxn--dnna-graingerxn--drbak-wuaxn--dyry-iraxn--e1a4churchateblobanazawanggoupilefrakkestadtvsamegawaxn--eckvdtc9dxn--efvn9svchitosetogakushimotoganexn--efvy88hadanorth-kazakhstanxn--ehqz56nxn--elqq16hadselbuyshouseshimonitayanagitappwritesthisblogdnsfor-better-thanhhoamishirasatohnoshookuwanakatsugawaxn--eveni-0qa01gaxn--f6qx53axn--fct429kopervikmpspawnbaseminexn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbciprianiigataipeigersundtwhitesnowflakeyword-onfabricafjsamnangerxn--fiq64bestbuyshoparenagareyamagicpatternsapporokunohealth-carereformemorialombardiademergentagents3-sa-east-1xn--fiqs8sveioxn--fiqz9svelvikongsvingerxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbremangerxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grajewolterskluwerxn--frna-woarais-certifiedxn--frya-hraxn--fzc2c9e2circleaninglugsjcbgmbhartinnxn--fzys8d69uvgmailxn--g2xx48ciscofreakadnsaliases121xn--gckr3f0fastvps-serveronakatombetsumitakagiizeaburxn--gecrj9cistrondheiminamiiseharaxn--ggaviika-8ya47haebaruericssonlanxesshimonosekikawaxn--gildeskl-g0axn--givuotna-8yanagawaxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-coolblogspotrentinoalto-adigexn--gmqw5axn--gnstigbestellen-zvbetaharanzanquangnamasteigenkainanaejrietiengiangjerdrumemsetaxiijimarnardalombardynamisches-dns3-us-east-2xn--gnstigliefern-wobiraxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3evenesvn-reposphinxn--45q11cooldns-cloudflareglobalashovhackclubartowhmincommbankazoxn--h2brj9c8citadelhichisoctrangminakamichikaiseiyoichipsamparaglidingmodellingmx-central-1xn--h3cuzk1dielddanuorrittogojomediatechnologyeongbukoryokamikawanehonbetsuwanouchikuhokuryugasakis-a-liberalxn--hbmer-xqaxn--hcesuolo-7ya35bhzc66xn--hebda8bialystokkepnord-aurdalwaysdatabase44-sandboxfuseekarasjohkameyamatotakadaustrheimbamblebtimnetzgorzeleccocottemprendealstahaugesundereggio-calabriap-southeast-5xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-fleeklogesquare7xn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyanaizuxn--io0a7is-foundationxn--j1adpmnxn--j1aefauskedsmokorsetagayaseralingenoaiusercontentranoyxn--j1ael8bielawalbrzychaselfiparliamentayninhachijoinmcdireggiocalabriauth-fipsiqcxjavald-aostatichostreak-linkanumazuryokozempresashibetsukumiyamagasakinkobayashimofusagaeroclubmedecin-berlindasdaejeonbuk0emmafann-arborlanddl-o-g-i-nayoro0o0g0xn--j1amhagakhanhhoabinhduongxn--j6w193gxn--jlq480n2rgxn--jlster-byandexcloudxn--jrpeland-54axn--jvr189miuraxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--4dbrk0cexn--koluokta-7ya57hagebostadxn--kprw13dxn--kpry57dxn--kput3is-gonexn--krager-gyaotsurnadalxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jejusgovtrafficmanagerxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasakaiminatoyotap-southeast-3xn--kvnangen-k0axn--l-1fairwindsurfbsbxn--1qqw23axn--l1accentureklamborghinikonantoshimatsusakahoginozawaonsennanmokurennebunkyonanaoshimamateramochausercontentuscanyxn--laheadju-7yasugithubusercontentushungryxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52biella-speziauthgear-stagingitpagemrappui-productions3-eu-west-1xn--lesund-huaxn--lgbbat1ad8jelasticbeanstalklabudhabikinokawabajddarvanedgecompute-1xn--lgrd-poacctfcloudflareanycastdlibestadultuvalle-daostakkomakis-an-actresshiraokamitondabayashiogamagoriziaxn--lhppi-xqaxn--linds-pratoyotomiyazakis-into-animeinforumzxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liaciticurus-4xn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddeswidnicanva-appspjelkavikomvuxn--42c2d9axn--mgb9awbfbx-osaveincloudyndns-picsbsarpsborgripeeweeklylotteryxn--mgba3a3ejtuxfamilyxn--mgba3a4f16axn--mgba3a4fra1-dell-ogliastrapiappleyxn--mgba7c0bbn0axn--mgbaam7a8haibarakitahiroshimap-south-2xn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00bielskoczow-credentialless-staticblitzlgjerstadiscordsays3-us-gov-east-1xn--mgbai9azgqp6jelenia-goraxn--mgbayh7gparallelxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexperimentswidnikitagatakinouexn--mgbpl2fhskosaigawaxn--mgbqly7c0a67fbcivilaviation-riopretogitsulidluyaniizaporizhzhiaxn--mgbqly7cvafricanvacode-builder-stg-builderxn--mgbt3dhdxn--mgbtf8fldrvaroyxn--mgbtx2bieszczadygeyachimataijiiyamanouchikujoinvilleirvikarasjoketokuyamarumorimachidauthgearapps-1and1xn--mgbx4cd0abogadobeaemcloud-ip6xn--mix082fbxosaves-the-whalessandria-trani-barletta-andriatranibarlettaandriaxn--mix891fedjeducatorprojectransfer-webapp-fipsavonatalxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44clanbibaiduckdnsamsclubin-vpndnsamsungotsukisofukushimaniwamannordreisa-hockeynutwentertainmentoystre-slidrettozawaxn--mkru45is-into-carshiratakahagiangxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-into-cartoonshishikuis-a-hunterxn--mosjen-eyasuokanmakiyokawaraxn--mot-tlavangenxn--mre-og-romsdal-qqbuserveboltuyenquangbinhthuanxn--msy-ula0haiduongxn--mtta-vrjjat-k7aflakstadaokayamazonaws-cloud9xn--muost-0qaxn--mxtq1miyazure-mobilexn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4gbriminiserverxn--nit225kosakaerodromegadgets-itcouldbeworfashionstorebaseballooningroks-theatrentin-sud-tirolxn--nmesjevuemie-tcbalsan-sudtirolkuszczytnoopstmnxn--nnx388axn--nodellogliastraderxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsienaharimakeupsunappgafanxn--o3cw4haiphongonnakayamangyshlakamaized-stagingxn--o3cyx2axn--od0algardxn--od0aq3bievathletajimabaria-vungtaudibleborkangereggioemiliaromagnarviikamiokameokamakurazakiwielunnerehabmereisenishinomiyashironomurauthordalandroidgnishiizunazukifr-1xn--ogbpf8flekkefjordxn--oppegrd-ixaxn--ostery-fyatsukannamimatakasugais-into-gamessinaplesknshisognexn--osyro-wuaxn--otu796dxn--p1acfolkswiebodzindependent-commissionxn--p1ais-leetrentinoaltoadigexn--pgbs0dhlxn--4it168dxn--porsgu-sta26fedorainfracloudfunctionsaxoxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cldmail-boxn--1lqs71durbanamexnetgamersandvikcoromantovalle-d-aostavernxn--qcka1pmclerkstagexn--qqqt11miyotamanoxn--qxa6axn--qxamjondalenxn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-localplayerxn--rennesy-v1axn--rhkkervju-01afedorapeopleikangerxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5navigationxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byatsushiroxn--rny31hair-surveillancexn--rovu88bifukagawalesundiscordsezpisdnipropetrovskypecorindependent-paneliv-cdn77-securealmesswithdns3-us-gov-west-1xn--rros-granvindafjordxn--rskog-uuaxn--rst-0navois-lostrolekamaishimodatexn--rsta-framercanvaswinoujsciencexn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawaraxn--s-1faithainguyenxn--s9brj9clever-clouderavpagexn--sandnessjen-ogbizxn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphicswisspockongsbergxn--skierv-utazurecontainerimakanegasakis-not-axn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navuotnaroyxn--slt-elabrdns-dynamic-dnsabruzzombieidskogasawarackmazerbaijan-mayenbaidarchitectestingrok-freeddnsgeekgalaxyzxn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbigv-infolldalomodxn--11b4c3discountry-snowplowiczeladzw-staticblitzxn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbiharstadotsubetsugaruhr-uni-bochumsochimkenthickarasuyamashikeu-south-2xn--srfold-byawatahamaxn--srreisa-q1axn--srum-gratis-a-bookkeepermarriottwmailxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbihoronobeokagakikiraraumaintenanceu1-plenittedalomzaporizhzhegurindependent-review3s3-us-west-1xn--stre-toten-zcbikedaemongolianishinoomotegoismailillehammerfeste-iparmatta-varjjathruherebungoonomutazas3-us-west-2xn--t60b56axn--tckwebthingsxn--tiq49xqyjellybeanxn--tjme-hraxn--tn0agrondarqtxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbioxn--trentin-sdtirol-7vbirkenesoddtangentapps3-website-ap-northeast-1xn--trentino-sd-tirol-c3bittermezproxyonagunicloudiscourses3-website-ap-southeast-1xn--trentino-sdtirol-szbjerkreimdbarcelonagawakuyabukihokuizumocha-sandboxmitakeharaudnedalnishigorlicebinordkapparisor-fronishiharakrehamnishiazaibradescotaribeiraogakicks-assncf-ipfs3-ap-southeast-2ixboxeroxajuniperecreationirasakibigawaknoluoktachikawafflecellpagest-mon-blogueurodirumaceratagajobojibmdeuxfleurs3-ap-southeast-1337xn--trentinosd-tirol-rzbjugnishinoshimatsuurautoscanaryggeemrnotebooks-prodeobservableusercontentatarantoyokawap-southeast-6116-bambinagisobetsuldalpha-myqnapcloudaccess3-ap-northeast-2038xn--trentinosdtirol-7vbloombergentingjesdalondonetskaratsuginamikatagamimozaokinawashirosatobishimadridvagsoyereithuathienhueusc-de-east-1xn--trentinsd-tirol-6vblushakotanishiokoppegardiscoverdalondrinapolicevervaultjeldsundisharparochernihivgubarclaycards3-fips-us-gov-east-1xn--trentinsdtirol-nsbmoattachments3-website-ap-southeast-2xn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvegaspydebergxn--uc0ay4axn--uist22hakatanorthflankazunotogawaxn--uisz3gxn--unjrga-rtarpitxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtxn--valle-d-aoste-ehboehringerikerxn--valleaoste-e7axn--valledaoste-ebbvadsoccerxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbms3-website-eu-west-1xn--vestvgy-ixa6oxn--vg-yiabmwcloudnonproddagestangevje-og-hornnes3-website-sa-east-1xn--vgan-qoaxn--vgsy-qoa0j0xn--vgu402cleverappsangotpantheonsitexn--vhquvelvetuckerxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bnrweatherchannelsdvrdns3-website-us-east-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1clickrisinglesjaguarvodkafkashiharaxn--wgbl6axn--xhq521bolognagasakikonaircraftraeumtgeradealerdalcest-le-patron-forgerockyotobetsucks3-website-us-gov-west-1xn--xkc2al3hye2axn--xkc2dl3a5ee0hakodatexn--y9a3aquarellebesbyencowayxn--yer-znavyxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4it797kontumintshizukuishimojis-a-landscaperspectakashimarshallstatebankhmelnytskyivalleedaostexn--ystre-slidre-ujbolzano-altoadigextraspace-to-rentalstomakomaibaravocats3-eu-west-2xn--zbx025dxn--zf0avxn--4pvxs4allxn--zfr164bomlodingenishitosashimizunaminamidaitomanaustdalopparachutingjovikareliancexnbayernxtooldevicexz \ No newline at end of file diff --git a/vendor/golang.org/x/net/publicsuffix/table.go b/vendor/golang.org/x/net/publicsuffix/table.go index 0fadf9527f7..a37663223aa 100644 --- a/vendor/golang.org/x/net/publicsuffix/table.go +++ b/vendor/golang.org/x/net/publicsuffix/table.go @@ -4,7 +4,7 @@ package publicsuffix import _ "embed" -const version = "publicsuffix.org's public_suffix_list.dat, git revision 2c960dac3d39ba521eb5db9da192968f5be0aded (2025-03-18T07:22:13Z)" +const version = "publicsuffix.org's public_suffix_list.dat, git revision d6c92f1bbb7433e5db7b8405c25d4035fb8ff376 (2026-02-06T07:36:33Z)" const ( nodesBits = 40 @@ -26,7 +26,7 @@ const ( ) // numTLD is the number of top level domains. -const numTLD = 1454 +const numTLD = 1450 // text is the combined text of all labels. // @@ -63,8 +63,8 @@ var nodes uint40String //go:embed data/children var children uint32String -// max children 870 (capacity 1023) -// max text offset 31785 (capacity 65535) +// max children 935 (capacity 1023) +// max text offset 32332 (capacity 65535) // max text length 31 (capacity 63) -// max hi 10100 (capacity 16383) -// max lo 10095 (capacity 16383) +// max hi 10533 (capacity 16383) +// max lo 10528 (capacity 16383) diff --git a/vendor/golang.org/x/sync/singleflight/singleflight.go b/vendor/golang.org/x/sync/singleflight/singleflight.go index 4051830982a..90ca138af31 100644 --- a/vendor/golang.org/x/sync/singleflight/singleflight.go +++ b/vendor/golang.org/x/sync/singleflight/singleflight.go @@ -22,7 +22,7 @@ var errGoexit = errors.New("runtime.Goexit was called") // A panicError is an arbitrary value recovered from a panic // with the stack trace during the execution of given function. type panicError struct { - value interface{} + value any stack []byte } @@ -40,7 +40,7 @@ func (p *panicError) Unwrap() error { return err } -func newPanicError(v interface{}) error { +func newPanicError(v any) error { stack := debug.Stack() // The first line of the stack trace is of the form "goroutine N [status]:" @@ -58,7 +58,7 @@ type call struct { // These fields are written once before the WaitGroup is done // and are only read after the WaitGroup is done. - val interface{} + val any err error // These fields are read and written with the singleflight @@ -78,7 +78,7 @@ type Group struct { // Result holds the results of Do, so they can be passed // on a channel. type Result struct { - Val interface{} + Val any Err error Shared bool } @@ -88,7 +88,7 @@ type Result struct { // time. If a duplicate comes in, the duplicate caller waits for the // original to complete and receives the same results. // The return value shared indicates whether v was given to multiple callers. -func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { +func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) @@ -118,7 +118,7 @@ func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, e // results when they are ready. // // The returned channel will not be closed. -func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result { +func (g *Group) DoChan(key string, fn func() (any, error)) <-chan Result { ch := make(chan Result, 1) g.mu.Lock() if g.m == nil { @@ -141,7 +141,7 @@ func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result } // doCall handles the single call for a key. -func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) { +func (g *Group) doCall(c *call, key string, fn func() (any, error)) { normalReturn := false recovered := false diff --git a/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s b/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s new file mode 100644 index 00000000000..e07fa75eb58 --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s @@ -0,0 +1,12 @@ +// Copyright 2024 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. + +//go:build darwin && arm64 && gc + +#include "textflag.h" + +TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_sysctlbyname(SB) +GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8 +DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB) diff --git a/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_arm64.go index 6d8eb784b5f..5fc09e2935d 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -44,14 +44,11 @@ func initOptions() { } func archInit() { - switch runtime.GOOS { - case "freebsd": + if runtime.GOOS == "freebsd" { readARM64Registers() - case "linux", "netbsd", "openbsd", "windows": + } else { + // Most platforms don't seem to allow directly reading these registers. doinit() - default: - // Many platforms don't seem to allow reading these registers. - setMinimalFeatures() } } diff --git a/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go new file mode 100644 index 00000000000..0b470744a0b --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go @@ -0,0 +1,67 @@ +// Copyright 2026 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. + +//go:build darwin && arm64 && gc + +package cpu + +func doinit() { + setMinimalFeatures() + + // The feature flags are explained in [Instruction Set Detection]. + // There are some differences between MacOS versions: + // + // MacOS 11 and 12 do not have "hw.optional" sysctl values for some of the features. + // + // MacOS 13 changed some of the naming conventions to align with ARM Architecture Reference Manual. + // For example "hw.optional.armv8_2_sha512" became "hw.optional.arm.FEAT_SHA512". + // It currently checks both to stay compatible with MacOS 11 and 12. + // The old names also work with MacOS 13, however it's not clear whether + // they will continue working with future OS releases. + // + // Once MacOS 12 is no longer supported the old names can be removed. + // + // [Instruction Set Detection]: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics + + // Encryption, hashing and checksum capabilities + + // For the following flags there are no MacOS 11 sysctl flags. + ARM64.HasAES = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_AES\x00")) + ARM64.HasPMULL = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_PMULL\x00")) + ARM64.HasSHA1 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA1\x00")) + ARM64.HasSHA2 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA256\x00")) + + ARM64.HasSHA3 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha3\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA3\x00")) + ARM64.HasSHA512 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha512\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA512\x00")) + + ARM64.HasCRC32 = darwinSysctlEnabled([]byte("hw.optional.armv8_crc32\x00")) + + // Atomic and memory ordering + ARM64.HasATOMICS = darwinSysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LSE\x00")) + ARM64.HasLRCPC = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LRCPC\x00")) + + // SIMD and floating point capabilities + ARM64.HasFPHP = darwinSysctlEnabled([]byte("hw.optional.neon_fp16\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FP16\x00")) + ARM64.HasASIMDHP = darwinSysctlEnabled([]byte("hw.optional.neon_hpfp\x00")) || darwinSysctlEnabled([]byte("hw.optional.AdvSIMD_HPFPCvt\x00")) + ARM64.HasASIMDRDM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_RDM\x00")) + ARM64.HasASIMDDP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DotProd\x00")) + ARM64.HasASIMDFHM = darwinSysctlEnabled([]byte("hw.optional.armv8_2_fhm\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FHM\x00")) + ARM64.HasI8MM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_I8MM\x00")) + + ARM64.HasJSCVT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_JSCVT\x00")) + ARM64.HasFCMA = darwinSysctlEnabled([]byte("hw.optional.armv8_3_compnum\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FCMA\x00")) + + // Miscellaneous + ARM64.HasDCPOP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DPB\x00")) + ARM64.HasEVTSTRM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_ECV\x00")) + ARM64.HasDIT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DIT\x00")) + + // Not supported, but added for completeness + ARM64.HasCPUID = false + + ARM64.HasSM3 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM3\x00")) + ARM64.HasSM4 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM4\x00")) + ARM64.HasSVE = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE\x00")) + ARM64.HasSVE2 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE2\x00")) +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go new file mode 100644 index 00000000000..4ee68e38d9b --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go @@ -0,0 +1,29 @@ +// Copyright 2026 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. + +//go:build darwin && arm64 && !gc + +package cpu + +func doinit() { + setMinimalFeatures() + + ARM64.HasASIMD = true + ARM64.HasFP = true + + // Go already assumes these to be available because they were on the M1 + // and these are supported on all Apple arm64 chips. + ARM64.HasAES = true + ARM64.HasPMULL = true + ARM64.HasSHA1 = true + ARM64.HasSHA2 = true + + if runtime.GOOS != "ios" { + // Apple A7 processors do not support these, however + // M-series SoCs are at least armv8.4-a + ARM64.HasCRC32 = true // armv8.1 + ARM64.HasATOMICS = true // armv8.2 + ARM64.HasJSCVT = true // armv8.3, if HasFP + } +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go index 7f1946780bd..05913081ec6 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go @@ -9,3 +9,4 @@ package cpu func getisar0() uint64 { return 0 } func getisar1() uint64 { return 0 } func getpfr0() uint64 { return 0 } +func getzfr0() uint64 { return 0 } diff --git a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go index ff74d7afa81..6c7c5bfd533 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !linux && !netbsd && !openbsd && !windows && arm64 +//go:build !darwin && !linux && !netbsd && !openbsd && !windows && arm64 package cpu -func doinit() {} +func doinit() { + setMinimalFeatures() +} diff --git a/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go b/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go new file mode 100644 index 00000000000..7b4e67ff9c9 --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go @@ -0,0 +1,54 @@ +// Copyright 2024 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. + +// Minimal copy from internal/cpu and runtime to make sysctl calls. + +//go:build darwin && arm64 && gc + +package cpu + +import ( + "syscall" + "unsafe" +) + +type Errno = syscall.Errno + +// adapted from internal/cpu/cpu_arm64_darwin.go +func darwinSysctlEnabled(name []byte) bool { + out := int32(0) + nout := unsafe.Sizeof(out) + if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil { + return false + } + return out > 0 +} + +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +var libc_sysctlbyname_trampoline_addr uintptr + +// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix +func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { + if _, _, err := syscall_syscall6( + libc_sysctlbyname_trampoline_addr, + uintptr(unsafe.Pointer(name)), + uintptr(unsafe.Pointer(old)), + uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), + uintptr(newlen), + 0, + ); err != 0 { + return err + } + + return nil +} + +//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib" + +// Implemented in the runtime package (runtime/sys_darwin.go) +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) + +//go:linkname syscall_syscall6 syscall.syscall6 diff --git a/vendor/golang.org/x/sys/plan9/syscall_plan9.go b/vendor/golang.org/x/sys/plan9/syscall_plan9.go index d079d8116e9..761912237fa 100644 --- a/vendor/golang.org/x/sys/plan9/syscall_plan9.go +++ b/vendor/golang.org/x/sys/plan9/syscall_plan9.go @@ -19,13 +19,7 @@ import ( // A Note is a string describing a process note. // It implements the os.Signal interface. -type Note string - -func (n Note) Signal() {} - -func (n Note) String() string { - return string(n) -} +type Note = syscall.Note var ( Stdin = 0 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index c1a46701719..45476a73c61 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -593,110 +593,115 @@ const ( ) const ( - NDA_UNSPEC = 0x0 - NDA_DST = 0x1 - NDA_LLADDR = 0x2 - NDA_CACHEINFO = 0x3 - NDA_PROBES = 0x4 - NDA_VLAN = 0x5 - NDA_PORT = 0x6 - NDA_VNI = 0x7 - NDA_IFINDEX = 0x8 - NDA_MASTER = 0x9 - NDA_LINK_NETNSID = 0xa - NDA_SRC_VNI = 0xb - NTF_USE = 0x1 - NTF_SELF = 0x2 - NTF_MASTER = 0x4 - NTF_PROXY = 0x8 - NTF_EXT_LEARNED = 0x10 - NTF_OFFLOADED = 0x20 - NTF_ROUTER = 0x80 - NUD_INCOMPLETE = 0x1 - NUD_REACHABLE = 0x2 - NUD_STALE = 0x4 - NUD_DELAY = 0x8 - NUD_PROBE = 0x10 - NUD_FAILED = 0x20 - NUD_NOARP = 0x40 - NUD_PERMANENT = 0x80 - NUD_NONE = 0x0 - IFA_UNSPEC = 0x0 - IFA_ADDRESS = 0x1 - IFA_LOCAL = 0x2 - IFA_LABEL = 0x3 - IFA_BROADCAST = 0x4 - IFA_ANYCAST = 0x5 - IFA_CACHEINFO = 0x6 - IFA_MULTICAST = 0x7 - IFA_FLAGS = 0x8 - IFA_RT_PRIORITY = 0x9 - IFA_TARGET_NETNSID = 0xa - IFAL_LABEL = 0x2 - IFAL_ADDRESS = 0x1 - RT_SCOPE_UNIVERSE = 0x0 - RT_SCOPE_SITE = 0xc8 - RT_SCOPE_LINK = 0xfd - RT_SCOPE_HOST = 0xfe - RT_SCOPE_NOWHERE = 0xff - RT_TABLE_UNSPEC = 0x0 - RT_TABLE_COMPAT = 0xfc - RT_TABLE_DEFAULT = 0xfd - RT_TABLE_MAIN = 0xfe - RT_TABLE_LOCAL = 0xff - RT_TABLE_MAX = 0xffffffff - RTA_UNSPEC = 0x0 - RTA_DST = 0x1 - RTA_SRC = 0x2 - RTA_IIF = 0x3 - RTA_OIF = 0x4 - RTA_GATEWAY = 0x5 - RTA_PRIORITY = 0x6 - RTA_PREFSRC = 0x7 - RTA_METRICS = 0x8 - RTA_MULTIPATH = 0x9 - RTA_FLOW = 0xb - RTA_CACHEINFO = 0xc - RTA_TABLE = 0xf - RTA_MARK = 0x10 - RTA_MFC_STATS = 0x11 - RTA_VIA = 0x12 - RTA_NEWDST = 0x13 - RTA_PREF = 0x14 - RTA_ENCAP_TYPE = 0x15 - RTA_ENCAP = 0x16 - RTA_EXPIRES = 0x17 - RTA_PAD = 0x18 - RTA_UID = 0x19 - RTA_TTL_PROPAGATE = 0x1a - RTA_IP_PROTO = 0x1b - RTA_SPORT = 0x1c - RTA_DPORT = 0x1d - RTN_UNSPEC = 0x0 - RTN_UNICAST = 0x1 - RTN_LOCAL = 0x2 - RTN_BROADCAST = 0x3 - RTN_ANYCAST = 0x4 - RTN_MULTICAST = 0x5 - RTN_BLACKHOLE = 0x6 - RTN_UNREACHABLE = 0x7 - RTN_PROHIBIT = 0x8 - RTN_THROW = 0x9 - RTN_NAT = 0xa - RTN_XRESOLVE = 0xb - SizeofNlMsghdr = 0x10 - SizeofNlMsgerr = 0x14 - SizeofRtGenmsg = 0x1 - SizeofNlAttr = 0x4 - SizeofRtAttr = 0x4 - SizeofIfInfomsg = 0x10 - SizeofIfAddrmsg = 0x8 - SizeofIfAddrlblmsg = 0xc - SizeofIfaCacheinfo = 0x10 - SizeofRtMsg = 0xc - SizeofRtNexthop = 0x8 - SizeofNdUseroptmsg = 0x10 - SizeofNdMsg = 0xc + NDA_UNSPEC = 0x0 + NDA_DST = 0x1 + NDA_LLADDR = 0x2 + NDA_CACHEINFO = 0x3 + NDA_PROBES = 0x4 + NDA_VLAN = 0x5 + NDA_PORT = 0x6 + NDA_VNI = 0x7 + NDA_IFINDEX = 0x8 + NDA_MASTER = 0x9 + NDA_LINK_NETNSID = 0xa + NDA_SRC_VNI = 0xb + NTF_USE = 0x1 + NTF_SELF = 0x2 + NTF_MASTER = 0x4 + NTF_PROXY = 0x8 + NTF_EXT_LEARNED = 0x10 + NTF_OFFLOADED = 0x20 + NTF_ROUTER = 0x80 + NUD_INCOMPLETE = 0x1 + NUD_REACHABLE = 0x2 + NUD_STALE = 0x4 + NUD_DELAY = 0x8 + NUD_PROBE = 0x10 + NUD_FAILED = 0x20 + NUD_NOARP = 0x40 + NUD_PERMANENT = 0x80 + NUD_NONE = 0x0 + IFA_UNSPEC = 0x0 + IFA_ADDRESS = 0x1 + IFA_LOCAL = 0x2 + IFA_LABEL = 0x3 + IFA_BROADCAST = 0x4 + IFA_ANYCAST = 0x5 + IFA_CACHEINFO = 0x6 + IFA_MULTICAST = 0x7 + IFA_FLAGS = 0x8 + IFA_RT_PRIORITY = 0x9 + IFA_TARGET_NETNSID = 0xa + IFAL_LABEL = 0x2 + IFAL_ADDRESS = 0x1 + RT_SCOPE_UNIVERSE = 0x0 + RT_SCOPE_SITE = 0xc8 + RT_SCOPE_LINK = 0xfd + RT_SCOPE_HOST = 0xfe + RT_SCOPE_NOWHERE = 0xff + RT_TABLE_UNSPEC = 0x0 + RT_TABLE_COMPAT = 0xfc + RT_TABLE_DEFAULT = 0xfd + RT_TABLE_MAIN = 0xfe + RT_TABLE_LOCAL = 0xff + RT_TABLE_MAX = 0xffffffff + RTA_UNSPEC = 0x0 + RTA_DST = 0x1 + RTA_SRC = 0x2 + RTA_IIF = 0x3 + RTA_OIF = 0x4 + RTA_GATEWAY = 0x5 + RTA_PRIORITY = 0x6 + RTA_PREFSRC = 0x7 + RTA_METRICS = 0x8 + RTA_MULTIPATH = 0x9 + RTA_FLOW = 0xb + RTA_CACHEINFO = 0xc + RTA_TABLE = 0xf + RTA_MARK = 0x10 + RTA_MFC_STATS = 0x11 + RTA_VIA = 0x12 + RTA_NEWDST = 0x13 + RTA_PREF = 0x14 + RTA_ENCAP_TYPE = 0x15 + RTA_ENCAP = 0x16 + RTA_EXPIRES = 0x17 + RTA_PAD = 0x18 + RTA_UID = 0x19 + RTA_TTL_PROPAGATE = 0x1a + RTA_IP_PROTO = 0x1b + RTA_SPORT = 0x1c + RTA_DPORT = 0x1d + RTN_UNSPEC = 0x0 + RTN_UNICAST = 0x1 + RTN_LOCAL = 0x2 + RTN_BROADCAST = 0x3 + RTN_ANYCAST = 0x4 + RTN_MULTICAST = 0x5 + RTN_BLACKHOLE = 0x6 + RTN_UNREACHABLE = 0x7 + RTN_PROHIBIT = 0x8 + RTN_THROW = 0x9 + RTN_NAT = 0xa + RTN_XRESOLVE = 0xb + PREFIX_UNSPEC = 0x0 + PREFIX_ADDRESS = 0x1 + PREFIX_CACHEINFO = 0x2 + SizeofNlMsghdr = 0x10 + SizeofNlMsgerr = 0x14 + SizeofRtGenmsg = 0x1 + SizeofNlAttr = 0x4 + SizeofRtAttr = 0x4 + SizeofIfInfomsg = 0x10 + SizeofPrefixmsg = 0xc + SizeofPrefixCacheinfo = 0x8 + SizeofIfAddrmsg = 0x8 + SizeofIfAddrlblmsg = 0xc + SizeofIfaCacheinfo = 0x10 + SizeofRtMsg = 0xc + SizeofRtNexthop = 0x8 + SizeofNdUseroptmsg = 0x10 + SizeofNdMsg = 0xc ) type NlMsghdr struct { @@ -735,6 +740,22 @@ type IfInfomsg struct { Change uint32 } +type Prefixmsg struct { + Family uint8 + Pad1 uint8 + Pad2 uint16 + Ifindex int32 + Type uint8 + Len uint8 + Flags uint8 + Pad3 uint8 +} + +type PrefixCacheinfo struct { + Preferred_time uint32 + Valid_time uint32 +} + type IfAddrmsg struct { Family uint8 Prefixlen uint8 diff --git a/vendor/golang.org/x/sys/windows/aliases.go b/vendor/golang.org/x/sys/windows/aliases.go index 16f90560a23..96317966e52 100644 --- a/vendor/golang.org/x/sys/windows/aliases.go +++ b/vendor/golang.org/x/sys/windows/aliases.go @@ -8,5 +8,6 @@ package windows import "syscall" +type Signal = syscall.Signal type Errno = syscall.Errno type SysProcAttr = syscall.SysProcAttr diff --git a/vendor/golang.org/x/sys/windows/registry/key.go b/vendor/golang.org/x/sys/windows/registry/key.go index 39aeeb644f5..7cc6ff3afa0 100644 --- a/vendor/golang.org/x/sys/windows/registry/key.go +++ b/vendor/golang.org/x/sys/windows/registry/key.go @@ -198,7 +198,20 @@ type KeyInfo struct { // ModTime returns the key's last write time. func (ki *KeyInfo) ModTime() time.Time { - return time.Unix(0, ki.lastWriteTime.Nanoseconds()) + lastHigh, lastLow := ki.lastWriteTime.HighDateTime, ki.lastWriteTime.LowDateTime + // 100-nanosecond intervals since January 1, 1601 + hsec := uint64(lastHigh)<<32 + uint64(lastLow) + // Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00 + // UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out + // of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64 + sec := int64(hsec/1e7) - 11644473600 + nsec := int64(hsec%1e7) * 100 + return time.Unix(sec, nsec) +} + +// modTimeZero reports whether the key's last write time is zero. +func (ki *KeyInfo) modTimeZero() bool { + return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0 } // Stat retrieves information about the open key k. diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 738a9f2121b..d766436587f 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -1490,20 +1490,6 @@ func Getgid() (gid int) { return -1 } func Getegid() (egid int) { return -1 } func Getgroups() (gids []int, err error) { return nil, syscall.EWINDOWS } -type Signal int - -func (s Signal) Signal() {} - -func (s Signal) String() string { - if 0 <= s && int(s) < len(signals) { - str := signals[s] - if str != "" { - return str - } - } - return "signal " + itoa(int(s)) -} - func LoadCreateSymbolicLink() error { return procCreateSymbolicLinkW.Find() } diff --git a/vendor/golang.org/x/tools/go/ast/inspector/cursor.go b/vendor/golang.org/x/tools/go/ast/inspector/cursor.go index 60ad425f343..239b10c4da0 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/cursor.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/cursor.go @@ -18,8 +18,11 @@ import ( // // Two Cursors compare equal if they represent the same node. // -// Call [Inspector.Root] to obtain a valid cursor for the virtual root -// node of the traversal. +// The zero value of Cursor is not valid. +// +// Call [Inspector.Root] to obtain a cursor for the virtual root node +// of the traversal. This is the sole valid cursor for which [Cursor.Node] +// returns nil. // // Use the following methods to navigate efficiently around the tree: // - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing]; @@ -37,7 +40,7 @@ type Cursor struct { index int32 // index of push node; -1 for virtual root node } -// Root returns a cursor for the virtual root node, +// Root returns a valid cursor for the virtual root node, // whose children are the files provided to [New]. // // Its [Cursor.Node] method return nil. @@ -61,14 +64,23 @@ func (in *Inspector) At(index int32) Cursor { return Cursor{in, index} } +// Valid reports whether the cursor is valid. +// The zero value of cursor is invalid. +// Unless otherwise documented, it is not safe to call +// any other method on an invalid cursor. +func (c Cursor) Valid() bool { + return c.in != nil +} + // Inspector returns the cursor's Inspector. +// It returns nil if the Cursor is not valid. func (c Cursor) Inspector() *Inspector { return c.in } // Index returns the index of this cursor position within the package. // // Clients should not assume anything about the numeric Index value // except that it increases monotonically throughout the traversal. -// It is provided for use with [At]. +// It is provided for use with [Inspector.At]. // // Index must not be called on the Root node. func (c Cursor) Index() int32 { @@ -89,7 +101,7 @@ func (c Cursor) Node() ast.Node { // String returns information about the cursor's node, if any. func (c Cursor) String() string { - if c.in == nil { + if !c.Valid() { return "(invalid)" } if c.index < 0 { @@ -233,6 +245,18 @@ func (c Cursor) ParentEdge() (edge.Kind, int) { return unpackEdgeKindAndIndex(events[pop].parent) } +// ParentEdgeKind returns the kind component of the result of [Cursor.ParentEdge]. +func (c Cursor) ParentEdgeKind() edge.Kind { + ek, _ := c.ParentEdge() + return ek +} + +// ParentEdgeIndex returns the index component of the result of [Cursor.ParentEdge]. +func (c Cursor) ParentEdgeIndex() int { + _, index := c.ParentEdge() + return index +} + // ChildAt returns the cursor for the child of the // current node identified by its edge and index. // The index must be -1 if the edge.Kind is not a slice. diff --git a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go index a703cdfcf90..b414d17ebd7 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go @@ -87,7 +87,7 @@ type event struct { // Type can be recovered from the sole bit in typ. // [Tried this, wasn't faster. --adonovan] -// Preorder visits all the nodes of the files supplied to New in +// Preorder visits all the nodes of the files supplied to [New] in // depth-first order. It calls f(n) for each node n before it visits // n's children. // @@ -133,7 +133,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { } } -// Nodes visits the nodes of the files supplied to New in depth-first +// Nodes visits the nodes of the files supplied to [New] in depth-first // order. It calls f(n, true) for each node n before it visits n's // children. If f returns true, Nodes invokes f recursively for each // of the non-nil children of the node, followed by a call of diff --git a/vendor/golang.org/x/tools/go/ast/inspector/iter.go b/vendor/golang.org/x/tools/go/ast/inspector/iter.go index c576dc70ac7..b68c553d413 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/iter.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/iter.go @@ -12,13 +12,31 @@ import ( ) // PreorderSeq returns an iterator that visits all the -// nodes of the files supplied to New in depth-first order. +// nodes of the files supplied to [New] in depth-first order. // It visits each node n before n's children. // The complete traversal sequence is determined by ast.Inspect. // -// The types argument, if non-empty, enables type-based -// filtering of events: only nodes whose type matches an -// element of the types slice are included in the sequence. +// The types argument, if non-empty, enables type-based filtering: +// only nodes whose type matches an element of the types slice are +// included in the sequence. +// +// Example: +// +// for call := range in.PreorderSeq((*ast.CallExpr)(nil)) { ... } +// +// The [All] function is more convenient if there is exactly one node type: +// +// for call := range All[*ast.CallExpr](in) { ... } +// +// See also the newer and more flexible [Cursor] API, which lets you +// start the traversal at an arbitrary node, and reports each matching +// node by its Cursor, enabling easier navigation. +// The above example would be written thus: +// +// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) { +// call := curCall.Node().(*ast.CallExpr) +// ... +// } func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] { // This implementation is identical to Preorder, @@ -53,6 +71,16 @@ func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] { // Example: // // for call := range All[*ast.CallExpr](in) { ... } +// +// See also the newer and more flexible [Cursor] API, which lets you +// start the traversal at an arbitrary node, and reports each matching +// node by its Cursor, enabling easier navigation. +// The above example would be written thus: +// +// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) { +// call := curCall.Node().(*ast.CallExpr) +// ... +// } func All[N interface { *S ast.Node diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index ff607389dac..b249a5c7efd 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -284,6 +284,8 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { } } + ld.externalDriver = external + return ld.refine(response) } @@ -692,10 +694,11 @@ type loaderPackage struct { type loader struct { pkgs map[string]*loaderPackage // keyed by Package.ID Config - sizes types.Sizes // non-nil if needed by mode - parseCache map[string]*parseValue - parseCacheMu sync.Mutex - exportMu sync.Mutex // enforces mutual exclusion of exportdata operations + sizes types.Sizes // non-nil if needed by mode + parseCache map[string]*parseValue + parseCacheMu sync.Mutex + exportMu sync.Mutex // enforces mutual exclusion of exportdata operations + externalDriver bool // true if an external GOPACKAGESDRIVER handled the request // Config.Mode contains the implied mode (see impliedLoadMode). // Implied mode contains all the fields we need the data for. @@ -1226,6 +1229,10 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { } if lpkg.Module != nil && lpkg.Module.GoVersion != "" { tc.GoVersion = "go" + lpkg.Module.GoVersion + } else if ld.externalDriver && lpkg.goVersion != 0 { + // Module information is missing when GOPACKAGESDRIVER is used, + // so use the go version from the driver response. + tc.GoVersion = fmt.Sprintf("go1.%d", lpkg.goVersion) } if (ld.Mode & typecheckCgo) != 0 { if !typesinternal.SetUsesCgo(tc) { diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index 6646bf55089..56723d1f82e 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -29,7 +29,6 @@ import ( "strconv" "strings" - "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typesinternal" ) @@ -281,10 +280,10 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { T := o.Type() if alias, ok := T.(*types.Alias); ok { - if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam); r != nil { + if r := findTypeParam(obj, alias.TypeParams(), path, opTypeParam); r != nil { return Path(r), nil } - if r := find(obj, aliases.Rhs(alias), append(path, opRhs)); r != nil { + if r := find(obj, alias.Rhs(), append(path, opRhs)); r != nil { return Path(r), nil } @@ -694,14 +693,11 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { case opRhs: if alias, ok := t.(*types.Alias); ok { - t = aliases.Rhs(alias) - } else if false && aliases.Enabled() { - // The Enabled check is too expensive, so for now we - // simply assume that aliases are not enabled. - // + t = alias.Rhs() + } else if false { // Now that go1.24 is assured, we should be able to - // replace this with "if true {", but it causes tests - // to fail. TODO(adonovan): investigate. + // replace this with "if true {", but it causes objectpath + // tests to fail. TODO(adonovan): investigate. return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t) } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases.go b/vendor/golang.org/x/tools/internal/aliases/aliases.go index b9425f5a209..a4ae04bc71a 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases.go @@ -9,30 +9,10 @@ import ( "go/types" ) -// Package aliases defines backward compatible shims -// for the types.Alias type representation added in 1.22. -// This defines placeholders for x/tools until 1.26. - -// NewAlias creates a new TypeName in Package pkg that +// New creates a new TypeName in Package pkg that // is an alias for the type rhs. -// -// The enabled parameter determines whether the resulting [TypeName]'s -// type is an [types.Alias]. Its value must be the result of a call to -// [Enabled], which computes the effective value of -// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled -// function is expensive and should be called once per task (e.g. -// package import), not once per call to NewAlias. -// -// Precondition: enabled || len(tparams)==0. -// If materialized aliases are disabled, there must not be any type parameters. -func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { - if enabled { - tname := types.NewTypeName(pos, pkg, name, nil) - SetTypeParams(types.NewAlias(tname, rhs), tparams) - return tname - } - if len(tparams) > 0 { - panic("cannot create an alias with type parameters when gotypesalias is not enabled") - } - return types.NewTypeName(pos, pkg, name, rhs) +func New(pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { + tname := types.NewTypeName(pos, pkg, name, nil) + types.NewAlias(tname, rhs).SetTypeParams(tparams) + return tname } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go deleted file mode 100644 index 7716a3331db..00000000000 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2024 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 aliases - -import ( - "go/ast" - "go/parser" - "go/token" - "go/types" -) - -// Rhs returns the type on the right-hand side of the alias declaration. -func Rhs(alias *types.Alias) types.Type { - if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok { - return alias.Rhs() // go1.23+ - } - - // go1.22's Alias didn't have the Rhs method, - // so Unalias is the best we can do. - return types.Unalias(alias) -} - -// TypeParams returns the type parameter list of the alias. -func TypeParams(alias *types.Alias) *types.TypeParamList { - if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok { - return alias.TypeParams() // go1.23+ - } - return nil -} - -// SetTypeParams sets the type parameters of the alias type. -func SetTypeParams(alias *types.Alias, tparams []*types.TypeParam) { - if alias, ok := any(alias).(interface { - SetTypeParams(tparams []*types.TypeParam) - }); ok { - alias.SetTypeParams(tparams) // go1.23+ - } else if len(tparams) > 0 { - panic("cannot set type parameters of an Alias type in go1.22") - } -} - -// TypeArgs returns the type arguments used to instantiate the Alias type. -func TypeArgs(alias *types.Alias) *types.TypeList { - if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok { - return alias.TypeArgs() // go1.23+ - } - return nil // empty (go1.22) -} - -// Origin returns the generic Alias type of which alias is an instance. -// If alias is not an instance of a generic alias, Origin returns alias. -func Origin(alias *types.Alias) *types.Alias { - if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok { - return alias.Origin() // go1.23+ - } - return alias // not an instance of a generic alias (go1.22) -} - -// Enabled reports whether [NewAlias] should create [types.Alias] types. -// -// This function is expensive! Call it sparingly. -func Enabled() bool { - // The only reliable way to compute the answer is to invoke go/types. - // We don't parse the GODEBUG environment variable, because - // (a) it's tricky to do so in a manner that is consistent - // with the godebug package; in particular, a simple - // substring check is not good enough. The value is a - // rightmost-wins list of options. But more importantly: - // (b) it is impossible to detect changes to the effective - // setting caused by os.Setenv("GODEBUG"), as happens in - // many tests. Therefore any attempt to cache the result - // is just incorrect. - fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", parser.SkipObjectResolution) - pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) - _, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias) - return enabled -} diff --git a/vendor/golang.org/x/tools/internal/event/core/event.go b/vendor/golang.org/x/tools/internal/event/core/event.go index ade5d1e799d..42c218818ab 100644 --- a/vendor/golang.org/x/tools/internal/event/core/event.go +++ b/vendor/golang.org/x/tools/internal/event/core/event.go @@ -7,6 +7,7 @@ package core import ( "fmt" + "iter" "time" "golang.org/x/tools/internal/event/label" @@ -34,10 +35,8 @@ func (ev Event) Format(f fmt.State, r rune) { if !ev.at.IsZero() { fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) } - for index := 0; ev.Valid(index); index++ { - if l := ev.Label(index); l.Valid() { - fmt.Fprintf(f, "\n\t%v", l) - } + for l := range ev.Labels() { + fmt.Fprintf(f, "\n\t%v", l) } } @@ -52,6 +51,22 @@ func (ev Event) Label(index int) label.Label { return ev.dynamic[index-len(ev.static)] } +// Labels returns an iterator over the event's valid labels. +func (ev Event) Labels() iter.Seq[label.Label] { + return func(yield func(label.Label) bool) { + for _, l := range ev.static { + if l.Valid() && !yield(l) { + return + } + } + for _, l := range ev.dynamic { + if l.Valid() && !yield(l) { + return + } + } + } +} + func (ev Event) Find(key label.Key) label.Label { for _, l := range ev.static { if l.Key() == key { diff --git a/vendor/golang.org/x/tools/internal/event/keys/keys.go b/vendor/golang.org/x/tools/internal/event/keys/keys.go index 4cfa51b6123..ac78bc3b9cf 100644 --- a/vendor/golang.org/x/tools/internal/event/keys/keys.go +++ b/vendor/golang.org/x/tools/internal/event/keys/keys.go @@ -6,14 +6,13 @@ package keys import ( "fmt" - "io" "math" "strconv" "golang.org/x/tools/internal/event/label" ) -// Value represents a key for untyped values. +// Value is a [label.Key] for untyped values. type Value struct { name string description string @@ -27,11 +26,11 @@ func New(name, description string) *Value { func (k *Value) Name() string { return k.name } func (k *Value) Description() string { return k.description } -func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { - fmt.Fprint(w, k.From(l)) +func (k *Value) Append(buf []byte, l label.Label) []byte { + return fmt.Append(buf, k.From(l)) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *Value) Get(lm label.Map) any { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -39,7 +38,7 @@ func (k *Value) Get(lm label.Map) any { return nil } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *Value) From(t label.Label) any { return t.UnpackValue() } // Of creates a new Label with this key and the supplied value. @@ -54,7 +53,7 @@ type Tag struct { description string } -// NewTag creates a new Key for tagging labels. +// NewTag creates a new [label.Key] for tagging labels. func NewTag(name, description string) *Tag { return &Tag{name: name, description: description} } @@ -62,18 +61,18 @@ func NewTag(name, description string) *Tag { func (k *Tag) Name() string { return k.name } func (k *Tag) Description() string { return k.description } -func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} +func (k *Tag) Append(buf []byte, l label.Label) []byte { return buf } // New creates a new Label with this key. func (k *Tag) New() label.Label { return label.OfValue(k, nil) } -// Int represents a key +// Int is a [label.Key] for signed integers. type Int struct { name string description string } -// NewInt creates a new Key for int values. +// NewInt returns a new [label.Key] for int64 values. func NewInt(name, description string) *Int { return &Int{name: name, description: description} } @@ -81,381 +80,92 @@ func NewInt(name, description string) *Int { func (k *Int) Name() string { return k.name } func (k *Int) Description() string { return k.description } -func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +func (k *Int) Append(buf []byte, l label.Label) []byte { + return strconv.AppendInt(buf, k.From(l), 10) } // Of creates a new Label with this key and the supplied value. -func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } +func (k *Int) Of(v int) label.Label { return k.Of64(int64(v)) } -// Get can be used to get a label for the key from a label.Map. -func (k *Int) Get(lm label.Map) int { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } - -// Int8 represents a key -type Int8 struct { - name string - description string -} - -// NewInt8 creates a new Key for int8 values. -func NewInt8(name, description string) *Int8 { - return &Int8{name: name, description: description} -} - -func (k *Int8) Name() string { return k.name } -func (k *Int8) Description() string { return k.description } - -func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int8) Get(lm label.Map) int8 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } - -// Int16 represents a key -type Int16 struct { - name string - description string -} - -// NewInt16 creates a new Key for int16 values. -func NewInt16(name, description string) *Int16 { - return &Int16{name: name, description: description} -} - -func (k *Int16) Name() string { return k.name } -func (k *Int16) Description() string { return k.description } - -func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int16) Get(lm label.Map) int16 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } - -// Int32 represents a key -type Int32 struct { - name string - description string -} +// Of64 creates a new Label with this key and the supplied value. +func (k *Int) Of64(v int64) label.Label { return label.Of64(k, uint64(v)) } -// NewInt32 creates a new Key for int32 values. -func NewInt32(name, description string) *Int32 { - return &Int32{name: name, description: description} -} - -func (k *Int32) Name() string { return k.name } -func (k *Int32) Description() string { return k.description } - -func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int32) Get(lm label.Map) int32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } - -// Int64 represents a key -type Int64 struct { - name string - description string -} - -// NewInt64 creates a new Key for int64 values. -func NewInt64(name, description string) *Int64 { - return &Int64{name: name, description: description} -} - -func (k *Int64) Name() string { return k.name } -func (k *Int64) Description() string { return k.description } - -func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, k.From(l), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int64) Get(lm label.Map) int64 { +// Get returns the label for the key of a label.Map. +func (k *Int) Get(lm label.Map) int64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } +// From returns the value of a Label. +func (k *Int) From(t label.Label) int64 { return int64(t.Unpack64()) } -// UInt represents a key -type UInt struct { +// Uint is a [label.Key] for unsigned integers. +type Uint struct { name string description string } -// NewUInt creates a new Key for uint values. -func NewUInt(name, description string) *UInt { - return &UInt{name: name, description: description} +// NewUint creates a new [label.Key] for unsigned values. +func NewUint(name, description string) *Uint { + return &Uint{name: name, description: description} } -func (k *UInt) Name() string { return k.name } -func (k *UInt) Description() string { return k.description } +func (k *Uint) Name() string { return k.name } +func (k *Uint) Description() string { return k.description } -func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +func (k *Uint) Append(buf []byte, l label.Label) []byte { + return strconv.AppendUint(buf, k.From(l), 10) } // Of creates a new Label with this key and the supplied value. -func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } +func (k *Uint) Of(v uint64) label.Label { return label.Of64(k, v) } -// Get can be used to get a label for the key from a label.Map. -func (k *UInt) Get(lm label.Map) uint { +// Get returns the label for the key of a label.Map. +func (k *Uint) Get(lm label.Map) uint64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } +// From returns the value of a Label. +func (k *Uint) From(t label.Label) uint64 { return t.Unpack64() } -// UInt8 represents a key -type UInt8 struct { +// Float is a label.Key for floating-point values. +type Float struct { name string description string } -// NewUInt8 creates a new Key for uint8 values. -func NewUInt8(name, description string) *UInt8 { - return &UInt8{name: name, description: description} +// NewFloat creates a new [label.Key] for floating-point values. +func NewFloat(name, description string) *Float { + return &Float{name: name, description: description} } -func (k *UInt8) Name() string { return k.name } -func (k *UInt8) Description() string { return k.description } +func (k *Float) Name() string { return k.name } +func (k *Float) Description() string { return k.description } -func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +func (k *Float) Append(buf []byte, l label.Label) []byte { + return strconv.AppendFloat(buf, k.From(l), 'E', -1, 64) } // Of creates a new Label with this key and the supplied value. -func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt8) Get(lm label.Map) uint8 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } - -// UInt16 represents a key -type UInt16 struct { - name string - description string -} - -// NewUInt16 creates a new Key for uint16 values. -func NewUInt16(name, description string) *UInt16 { - return &UInt16{name: name, description: description} -} - -func (k *UInt16) Name() string { return k.name } -func (k *UInt16) Description() string { return k.description } - -func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt16) Get(lm label.Map) uint16 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } - -// UInt32 represents a key -type UInt32 struct { - name string - description string -} - -// NewUInt32 creates a new Key for uint32 values. -func NewUInt32(name, description string) *UInt32 { - return &UInt32{name: name, description: description} -} - -func (k *UInt32) Name() string { return k.name } -func (k *UInt32) Description() string { return k.description } - -func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt32) Get(lm label.Map) uint32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } - -// UInt64 represents a key -type UInt64 struct { - name string - description string -} - -// NewUInt64 creates a new Key for uint64 values. -func NewUInt64(name, description string) *UInt64 { - return &UInt64{name: name, description: description} -} - -func (k *UInt64) Name() string { return k.name } -func (k *UInt64) Description() string { return k.description } - -func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, k.From(l), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt64) Get(lm label.Map) uint64 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } - -// Float32 represents a key -type Float32 struct { - name string - description string -} - -// NewFloat32 creates a new Key for float32 values. -func NewFloat32(name, description string) *Float32 { - return &Float32{name: name, description: description} -} - -func (k *Float32) Name() string { return k.name } -func (k *Float32) Description() string { return k.description } - -func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Float32) Of(v float32) label.Label { - return label.Of64(k, uint64(math.Float32bits(v))) -} - -// Get can be used to get a label for the key from a label.Map. -func (k *Float32) Get(lm label.Map) float32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Float32) From(t label.Label) float32 { - return math.Float32frombits(uint32(t.Unpack64())) -} - -// Float64 represents a key -type Float64 struct { - name string - description string -} - -// NewFloat64 creates a new Key for int64 values. -func NewFloat64(name, description string) *Float64 { - return &Float64{name: name, description: description} -} - -func (k *Float64) Name() string { return k.name } -func (k *Float64) Description() string { return k.description } - -func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Float64) Of(v float64) label.Label { +func (k *Float) Of(v float64) label.Label { return label.Of64(k, math.Float64bits(v)) } -// Get can be used to get a label for the key from a label.Map. -func (k *Float64) Get(lm label.Map) float64 { +// Get returns the label for the key of a label.Map. +func (k *Float) Get(lm label.Map) float64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *Float64) From(t label.Label) float64 { +// From returns the value of a Label. +func (k *Float) From(t label.Label) float64 { return math.Float64frombits(t.Unpack64()) } @@ -473,14 +183,14 @@ func NewString(name, description string) *String { func (k *String) Name() string { return k.name } func (k *String) Description() string { return k.description } -func (k *String) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendQuote(buf, k.From(l))) +func (k *String) Append(buf []byte, l label.Label) []byte { + return strconv.AppendQuote(buf, k.From(l)) } // Of creates a new Label with this key and the supplied value. func (k *String) Of(v string) label.Label { return label.OfString(k, v) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *String) Get(lm label.Map) string { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -488,53 +198,16 @@ func (k *String) Get(lm label.Map) string { return "" } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *String) From(t label.Label) string { return t.UnpackString() } -// Boolean represents a key -type Boolean struct { - name string - description string -} - -// NewBoolean creates a new Key for bool values. -func NewBoolean(name, description string) *Boolean { - return &Boolean{name: name, description: description} -} - -func (k *Boolean) Name() string { return k.name } -func (k *Boolean) Description() string { return k.description } - -func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendBool(buf, k.From(l))) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Boolean) Of(v bool) label.Label { - if v { - return label.Of64(k, 1) - } - return label.Of64(k, 0) -} - -// Get can be used to get a label for the key from a label.Map. -func (k *Boolean) Get(lm label.Map) bool { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return false -} - -// From can be used to get a value from a Label. -func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } - // Error represents a key type Error struct { name string description string } -// NewError creates a new Key for int64 values. +// NewError returns a new [label.Key] for error values. func NewError(name, description string) *Error { return &Error{name: name, description: description} } @@ -542,14 +215,14 @@ func NewError(name, description string) *Error { func (k *Error) Name() string { return k.name } func (k *Error) Description() string { return k.description } -func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { - io.WriteString(w, k.From(l).Error()) +func (k *Error) Append(buf []byte, l label.Label) []byte { + return append(buf, k.From(l).Error()...) } -// Of creates a new Label with this key and the supplied value. +// Of returns a new Label with this key and the supplied value. func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *Error) Get(lm label.Map) error { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -557,7 +230,7 @@ func (k *Error) Get(lm label.Map) error { return nil } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *Error) From(t label.Label) error { err, _ := t.UnpackValue().(error) return err diff --git a/vendor/golang.org/x/tools/internal/event/label/label.go b/vendor/golang.org/x/tools/internal/event/label/label.go index c37584af943..e84226f879b 100644 --- a/vendor/golang.org/x/tools/internal/event/label/label.go +++ b/vendor/golang.org/x/tools/internal/event/label/label.go @@ -19,12 +19,8 @@ type Key interface { Name() string // Description returns a string that can be used to describe the value. Description() string - - // Format is used in formatting to append the value of the label to the - // supplied buffer. - // The formatter may use the supplied buf as a scratch area to avoid - // allocations. - Format(w io.Writer, buf []byte, l Label) + // Append appends the formatted value of the label to the supplied buffer. + Append(buf []byte, l Label) []byte } // Label holds a key and value pair. @@ -131,8 +127,7 @@ func (t Label) Format(f fmt.State, r rune) { } io.WriteString(f, t.Key().Name()) io.WriteString(f, "=") - var buf [128]byte - t.Key().Format(f, buf[:0], t) + f.Write(t.Key().Append(nil, t)) // ignore error } func (l *list) Valid(index int) bool { diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 2bef2b058ba..4c9450f4eed 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -242,7 +242,6 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/internal/aliases" ) // IExportShallow encodes "shallow" export data for the specified package. @@ -767,11 +766,11 @@ func (p *iexporter) doDecl(obj types.Object) { } if obj.IsAlias() { - alias, materialized := t.(*types.Alias) // may fail when aliases are not enabled + alias, materialized := t.(*types.Alias) // perhaps false for certain built-ins? var tparams *types.TypeParamList if materialized { - tparams = aliases.TypeParams(alias) + tparams = alias.TypeParams() } if tparams.Len() == 0 { w.tag(aliasTag) @@ -785,7 +784,7 @@ func (p *iexporter) doDecl(obj types.Object) { if materialized { // Preserve materialized aliases, // even of non-exported types. - t = aliases.Rhs(alias) + t = alias.Rhs() } w.typ(t, obj.Pkg()) break @@ -1011,11 +1010,11 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { } switch t := t.(type) { case *types.Alias: - if targs := aliases.TypeArgs(t); targs.Len() > 0 { + if targs := t.TypeArgs(); targs.Len() > 0 { w.startType(instanceType) w.pos(t.Obj().Pos()) w.typeList(targs, pkg) - w.typ(aliases.Origin(t), pkg) + w.typ(t.Origin(), pkg) return } w.startType(aliasType) diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 4d6d50094a0..1ee4e935491 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -210,7 +210,6 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte p := iimporter{ version: int(version), ipath: path, - aliases: aliases.Enabled(), shallow: shallow, reportf: reportf, @@ -370,7 +369,6 @@ type iimporter struct { version int ipath string - aliases bool shallow bool reportf ReportFunc // if non-nil, used to report bugs @@ -576,7 +574,7 @@ func (r *importReader) obj(pkg *types.Package, name string) { tparams = r.tparamList() } typ := r.typ() - obj := aliases.NewAlias(r.p.aliases, pos, pkg, name, typ, tparams) + obj := aliases.New(pos, pkg, name, typ, tparams) markBlack(obj) // workaround for golang/go#69912 r.declare(obj) diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go index 37b4a39e9e1..2e0d80585f3 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go @@ -26,7 +26,6 @@ type pkgReader struct { ctxt *types.Context imports map[string]*types.Package // previously imported packages, indexed by path - aliases bool // create types.Alias nodes // lazily initialized arrays corresponding to the unified IR // PosBase, Pkg, and Type sections, respectively. @@ -98,7 +97,6 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st ctxt: ctxt, imports: imports, - aliases: aliases.Enabled(), posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)), pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)), @@ -539,7 +537,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { tparams = r.typeParamNames() } typ := r.typ() - declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ, tparams)) + declare(aliases.New(pos, objPkg, objName, typ, tparams)) case pkgbits.ObjConst: pos := r.pos() diff --git a/vendor/golang.org/x/tools/internal/stdlib/deps.go b/vendor/golang.org/x/tools/internal/stdlib/deps.go index f41431c949c..dacfc1dfffc 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/deps.go +++ b/vendor/golang.org/x/tools/internal/stdlib/deps.go @@ -307,7 +307,7 @@ var deps = [...]pkginfo{ {"net/textproto", "\x02\x01q\x03\x83\x01\f\n-\x01\x02\x15"}, {"net/url", "t\x03Fc\v\x10\x02\x01\x17"}, {"os", "t+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\r\x06"}, - {"os/exec", "\x03\ngI'\x01\x15\x01+\x06\a\n\x01\x04\r"}, + {"os/exec", "\x03\ngI'\x01\x15\x01+\x06\a\n\x01\x03\x01\r"}, {"os/exec/internal/fdtest", "\xc2\x02"}, {"os/signal", "\r\x99\x02\x15\x05\x02"}, {"os/user", "\x02\x01q\x03\x83\x01,\r\n\x01\x02"}, diff --git a/vendor/golang.org/x/tools/internal/typeparams/free.go b/vendor/golang.org/x/tools/internal/typeparams/free.go index 709d2fc1447..4c391876e4a 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/free.go +++ b/vendor/golang.org/x/tools/internal/typeparams/free.go @@ -6,8 +6,6 @@ package typeparams import ( "go/types" - - "golang.org/x/tools/internal/aliases" ) // Free is a memoization of the set of free type parameters within a @@ -38,7 +36,7 @@ func (w *Free) Has(typ types.Type) (res bool) { break case *types.Alias: - if aliases.TypeParams(t).Len() > aliases.TypeArgs(t).Len() { + if t.TypeParams().Len() > t.TypeArgs().Len() { return true // This is an uninstantiated Alias. } // The expansion of an alias can have free type parameters, diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index 51001666ef1..7112318fc2a 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -25,7 +25,6 @@ import ( "reflect" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/aliases" ) func SetUsesCgo(conf *types.Config) bool { @@ -142,7 +141,7 @@ var ( func Origin(t NamedOrAlias) NamedOrAlias { switch t := t.(type) { case *types.Alias: - return aliases.Origin(t) + return t.Origin() case *types.Named: return t.Origin() } diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index e8dc791299e..7ad6fb44ca8 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -88,6 +88,22 @@ var ( // feature can be disabled by setting the environment variable // GRPC_EXPERIMENTAL_PF_WEIGHTED_SHUFFLING to "false". PickFirstWeightedShuffling = boolFromEnv("GRPC_EXPERIMENTAL_PF_WEIGHTED_SHUFFLING", true) + + // DisableStrictPathChecking indicates whether strict path checking is + // disabled. This feature can be disabled by setting the environment + // variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to "true". + // + // When strict path checking is enabled, gRPC will reject requests with + // paths that do not conform to the gRPC over HTTP/2 specification found at + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md. + // + // When disabled, gRPC will allow paths that do not contain a leading slash. + // Enabling strict path checking is recommended for security reasons, as it + // prevents potential path traversal vulnerabilities. + // + // A future release will remove this environment variable, enabling strict + // path checking behavior unconditionally. + DisableStrictPathChecking = boolFromEnv("GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING", false) ) func boolFromEnv(envVar string, def bool) bool { diff --git a/vendor/google.golang.org/grpc/internal/transport/client_stream.go b/vendor/google.golang.org/grpc/internal/transport/client_stream.go index 980452519ea..cd8152ef13c 100644 --- a/vendor/google.golang.org/grpc/internal/transport/client_stream.go +++ b/vendor/google.golang.org/grpc/internal/transport/client_stream.go @@ -24,6 +24,7 @@ import ( "golang.org/x/net/http2" "google.golang.org/grpc/mem" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" "google.golang.org/grpc/status" ) @@ -46,10 +47,11 @@ type ClientStream struct { // meaningful after headerChan is closed (always call waitOnHeader() before // reading its value). headerValid bool - noHeaders bool // set if the client never received headers (set only after the stream is done). - headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times. - bytesReceived atomic.Bool // indicates whether any bytes have been received on this stream - unprocessed atomic.Bool // set if the server sends a refused stream or GOAWAY including this stream + noHeaders bool // set if the client never received headers (set only after the stream is done). + headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times. + bytesReceived atomic.Bool // indicates whether any bytes have been received on this stream + unprocessed atomic.Bool // set if the server sends a refused stream or GOAWAY including this stream + statsHandler stats.Handler // nil for internal streams (e.g., health check, ORCA) where telemetry is not supported. } // Read reads an n byte message from the input stream. diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 38ca031af64..37b1acc340b 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -478,7 +478,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts return t, nil } -func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *ClientStream { +func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr, handler stats.Handler) *ClientStream { // TODO(zhaoq): Handle uint32 overflow of Stream.id. s := &ClientStream{ Stream: Stream{ @@ -486,10 +486,11 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *ClientSt sendCompress: callHdr.SendCompress, contentSubtype: callHdr.ContentSubtype, }, - ct: t, - done: make(chan struct{}), - headerChan: make(chan struct{}), - doneFunc: callHdr.DoneFunc, + ct: t, + done: make(chan struct{}), + headerChan: make(chan struct{}), + doneFunc: callHdr.DoneFunc, + statsHandler: handler, } s.Stream.buf.init() s.Stream.wq.init(defaultWriteQuota, s.done) @@ -744,7 +745,7 @@ func (e NewStreamError) Error() string { // NewStream creates a stream and registers it into the transport as "active" // streams. All non-nil errors returned will be *NewStreamError. -func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientStream, error) { +func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler stats.Handler) (*ClientStream, error) { ctx = peer.NewContext(ctx, t.Peer()) // ServerName field of the resolver returned address takes precedence over @@ -781,7 +782,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientS if err != nil { return nil, &NewStreamError{Err: err, AllowTransparentRetry: false} } - s := t.newStream(ctx, callHdr) + s := t.newStream(ctx, callHdr, handler) cleanup := func(err error) { if s.swapState(streamDone) == streamDone { // If it was already done, return. @@ -902,7 +903,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientS return nil, &NewStreamError{Err: ErrConnClosing, AllowTransparentRetry: true} } } - if t.statsHandler != nil { + if s.statsHandler != nil { header, ok := metadata.FromOutgoingContext(ctx) if ok { header.Set("user-agent", t.userAgent) @@ -911,7 +912,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientS } // Note: The header fields are compressed with hpack after this call returns. // No WireLength field is set here. - t.statsHandler.HandleRPC(s.ctx, &stats.OutHeader{ + s.statsHandler.HandleRPC(s.ctx, &stats.OutHeader{ Client: true, FullMethod: callHdr.Method, RemoteAddr: t.remoteAddr, @@ -1587,16 +1588,16 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } } - if t.statsHandler != nil { + if s.statsHandler != nil { if !endStream { - t.statsHandler.HandleRPC(s.ctx, &stats.InHeader{ + s.statsHandler.HandleRPC(s.ctx, &stats.InHeader{ Client: true, WireLength: int(frame.Header().Length), Header: metadata.MD(mdata).Copy(), Compression: s.recvCompress, }) } else { - t.statsHandler.HandleRPC(s.ctx, &stats.InTrailer{ + s.statsHandler.HandleRPC(s.ctx, &stats.InTrailer{ Client: true, WireLength: int(frame.Header().Length), Trailer: metadata.MD(mdata).Copy(), diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index 10b9155f093..b86094da943 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -617,7 +617,7 @@ type ClientTransport interface { GracefulClose() // NewStream creates a Stream for an RPC. - NewStream(ctx context.Context, callHdr *CallHdr) (*ClientStream, error) + NewStream(ctx context.Context, callHdr *CallHdr, handler stats.Handler) (*ClientStream, error) // Error returns a channel that is closed when some I/O error // happens. Typically the caller should have a goroutine to monitor diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 1b5cefe8171..8efb29a7b95 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -42,6 +42,7 @@ import ( "google.golang.org/grpc/internal" "google.golang.org/grpc/internal/binarylog" "google.golang.org/grpc/internal/channelz" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcutil" istats "google.golang.org/grpc/internal/stats" @@ -149,6 +150,8 @@ type Server struct { serverWorkerChannel chan func() serverWorkerChannelClose func() + + strictPathCheckingLogEmitted atomic.Bool } type serverOptions struct { @@ -1762,6 +1765,24 @@ func (s *Server) processStreamingRPC(ctx context.Context, stream *transport.Serv return ss.s.WriteStatus(statusOK) } +func (s *Server) handleMalformedMethodName(stream *transport.ServerStream, ti *traceInfo) { + if ti != nil { + ti.tr.LazyLog(&fmtStringer{"Malformed method name %q", []any{stream.Method()}}, true) + ti.tr.SetError() + } + errDesc := fmt.Sprintf("malformed method name: %q", stream.Method()) + if err := stream.WriteStatus(status.New(codes.Unimplemented, errDesc)); err != nil { + if ti != nil { + ti.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true) + ti.tr.SetError() + } + channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream failed to write status: %v", err) + } + if ti != nil { + ti.tr.Finish() + } +} + func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) { ctx := stream.Context() ctx = contextWithServer(ctx, s) @@ -1782,26 +1803,30 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Ser } sm := stream.Method() - if sm != "" && sm[0] == '/' { + if sm == "" { + s.handleMalformedMethodName(stream, ti) + return + } + if sm[0] != '/' { + // TODO(easwars): Add a link to the CVE in the below log messages once + // published. + if envconfig.DisableStrictPathChecking { + if old := s.strictPathCheckingLogEmitted.Swap(true); !old { + channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream received malformed method name %q. Allowing it because the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING is set to true, but this option will be removed in a future release.", sm) + } + } else { + if old := s.strictPathCheckingLogEmitted.Swap(true); !old { + channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream rejected malformed method name %q. To temporarily allow such requests, set the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to true. Note that this is not recommended as it may allow requests to bypass security policies.", sm) + } + s.handleMalformedMethodName(stream, ti) + return + } + } else { sm = sm[1:] } pos := strings.LastIndex(sm, "/") if pos == -1 { - if ti != nil { - ti.tr.LazyLog(&fmtStringer{"Malformed method name %q", []any{sm}}, true) - ti.tr.SetError() - } - errDesc := fmt.Sprintf("malformed method name: %q", stream.Method()) - if err := stream.WriteStatus(status.New(codes.Unimplemented, errDesc)); err != nil { - if ti != nil { - ti.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true) - ti.tr.SetError() - } - channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream failed to write status: %v", err) - } - if ti != nil { - ti.tr.Finish() - } + s.handleMalformedMethodName(stream, ti) return } service := sm[:pos] diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index f92102fb4fb..eedb5f9b99c 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -548,7 +548,7 @@ func (a *csAttempt) newStream() error { } } } - s, err := a.transport.NewStream(a.ctx, cs.callHdr) + s, err := a.transport.NewStream(a.ctx, cs.callHdr, a.statsHandler) if err != nil { nse, ok := err.(*transport.NewStreamError) if !ok { @@ -1354,7 +1354,8 @@ func newNonRetryClientStream(ctx context.Context, desc *StreamDesc, method strin transport: t, } - s, err := as.transport.NewStream(as.ctx, as.callHdr) + // nil stats handler: internal streams like health and ORCA do not support telemetry. + s, err := as.transport.NewStream(as.ctx, as.callHdr, nil) if err != nil { err = toRPCErr(err) return nil, err diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index c1225b91036..76c2eed773a 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.79.1" +const Version = "1.79.3" diff --git a/vendor/modules.txt b/vendor/modules.txt index b9913c1fe98..d29d118c6de 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -88,7 +88,7 @@ github.com/alexedwards/argon2id # github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 ## explicit github.com/amoghe/go-crypt -# github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op +# github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op ## explicit; go 1.20 github.com/antithesishq/antithesis-sdk-go/assert github.com/antithesishq/antithesis-sdk-go/internal @@ -303,7 +303,7 @@ github.com/cespare/xxhash/v2 # github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 ## explicit github.com/cevaris/ordered_map -# github.com/cloudflare/circl v1.6.1 +# github.com/cloudflare/circl v1.6.3 ## explicit; go 1.22.0 github.com/cloudflare/circl/dh/x25519 github.com/cloudflare/circl/dh/x448 @@ -849,7 +849,7 @@ github.com/justinas/alice # github.com/kevinburke/ssh_config v1.2.0 ## explicit github.com/kevinburke/ssh_config -# github.com/klauspost/compress v1.18.3 +# github.com/klauspost/compress v1.18.4 ## explicit; go 1.23 github.com/klauspost/compress github.com/klauspost/compress/flate @@ -1094,11 +1094,11 @@ github.com/mschoch/smat # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg -# github.com/nats-io/jwt/v2 v2.8.0 -## explicit; go 1.23.0 +# github.com/nats-io/jwt/v2 v2.8.1 +## explicit; go 1.25.0 github.com/nats-io/jwt/v2 -# github.com/nats-io/nats-server/v2 v2.12.4 -## explicit; go 1.24.0 +# github.com/nats-io/nats-server/v2 v2.12.6 +## explicit; go 1.25.0 github.com/nats-io/nats-server/v2/conf github.com/nats-io/nats-server/v2/internal/fastrand github.com/nats-io/nats-server/v2/internal/ldap @@ -1115,13 +1115,13 @@ github.com/nats-io/nats-server/v2/server/stree github.com/nats-io/nats-server/v2/server/sysmem github.com/nats-io/nats-server/v2/server/thw github.com/nats-io/nats-server/v2/server/tpm -# github.com/nats-io/nats.go v1.48.0 -## explicit; go 1.23.0 +# github.com/nats-io/nats.go v1.49.0 +## explicit; go 1.24.0 github.com/nats-io/nats.go github.com/nats-io/nats.go/encoders/builtin github.com/nats-io/nats.go/internal/parser github.com/nats-io/nats.go/util -# github.com/nats-io/nkeys v0.4.12 +# github.com/nats-io/nkeys v0.4.15 ## explicit; go 1.24.0 github.com/nats-io/nkeys # github.com/nats-io/nuid v1.0.1 @@ -1799,8 +1799,8 @@ github.com/rs/zerolog/internal/cbor github.com/rs/zerolog/internal/json github.com/rs/zerolog/log github.com/rs/zerolog/pkgerrors -# github.com/russellhaering/goxmldsig v1.5.0 -## explicit; go 1.21.0 +# github.com/russellhaering/goxmldsig v1.6.0 +## explicit; go 1.23.0 github.com/russellhaering/goxmldsig github.com/russellhaering/goxmldsig/etreeutils github.com/russellhaering/goxmldsig/types @@ -2242,8 +2242,8 @@ go.yaml.in/yaml/v2 # go.yaml.in/yaml/v3 v3.0.4 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/crypto v0.48.0 -## explicit; go 1.24.0 +# golang.org/x/crypto v0.49.0 +## explicit; go 1.25.0 golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt golang.org/x/crypto/blake2b @@ -2278,8 +2278,8 @@ golang.org/x/exp/slices golang.org/x/exp/slog golang.org/x/exp/slog/internal golang.org/x/exp/slog/internal/buffer -# golang.org/x/image v0.36.0 -## explicit; go 1.24.0 +# golang.org/x/image v0.38.0 +## explicit; go 1.25.0 golang.org/x/image/bmp golang.org/x/image/ccitt golang.org/x/image/font @@ -2294,13 +2294,13 @@ golang.org/x/image/vector golang.org/x/image/vp8 golang.org/x/image/vp8l golang.org/x/image/webp -# golang.org/x/mod v0.32.0 +# golang.org/x/mod v0.33.0 ## explicit; go 1.24.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.49.0 -## explicit; go 1.24.0 +# golang.org/x/net v0.51.0 +## explicit; go 1.25.0 golang.org/x/net/bpf golang.org/x/net/context golang.org/x/net/context/ctxhttp @@ -2313,6 +2313,7 @@ golang.org/x/net/http2/h2c golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/httpcommon +golang.org/x/net/internal/httpsfv golang.org/x/net/internal/iana golang.org/x/net/internal/socket golang.org/x/net/internal/socks @@ -2327,13 +2328,13 @@ golang.org/x/net/trace ## explicit; go 1.24.0 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.19.0 -## explicit; go 1.24.0 +# golang.org/x/sync v0.20.0 +## explicit; go 1.25.0 golang.org/x/sync/errgroup golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.41.0 -## explicit; go 1.24.0 +# golang.org/x/sys v0.42.0 +## explicit; go 1.25.0 golang.org/x/sys/cpu golang.org/x/sys/execabs golang.org/x/sys/plan9 @@ -2343,11 +2344,11 @@ golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc golang.org/x/sys/windows/svc/eventlog golang.org/x/sys/windows/svc/mgr -# golang.org/x/term v0.40.0 -## explicit; go 1.24.0 +# golang.org/x/term v0.41.0 +## explicit; go 1.25.0 golang.org/x/term -# golang.org/x/text v0.34.0 -## explicit; go 1.24.0 +# golang.org/x/text v0.35.0 +## explicit; go 1.25.0 golang.org/x/text/cases golang.org/x/text/encoding golang.org/x/text/encoding/charmap @@ -2371,10 +2372,10 @@ golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm golang.org/x/text/width -# golang.org/x/time v0.14.0 -## explicit; go 1.24.0 +# golang.org/x/time v0.15.0 +## explicit; go 1.25.0 golang.org/x/time/rate -# golang.org/x/tools v0.41.0 +# golang.org/x/tools v0.42.0 ## explicit; go 1.24.0 golang.org/x/tools/cover golang.org/x/tools/go/ast/astutil @@ -2413,7 +2414,7 @@ google.golang.org/genproto/googleapis/api/httpbody ## explicit; go 1.24.0 google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.79.1 +# google.golang.org/grpc v1.79.3 ## explicit; go 1.24.0 google.golang.org/grpc google.golang.org/grpc/attributes