diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..f3afd16ec --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @livekit/cs-devs diff --git a/.github/workflows/buildtest.yaml b/.github/workflows/buildtest.yaml index be0e93d8f..284ecf87a 100644 --- a/.github/workflows/buildtest.yaml +++ b/.github/workflows/buildtest.yaml @@ -71,7 +71,7 @@ jobs: # Upload the original go test log as an artifact for later review. - name: Upload test log - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: always() with: name: test-log diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index feefd280f..1ee39fe6f 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -62,14 +62,14 @@ jobs: - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push id: docker_build - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 44b98294b..22f3d0e4a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -53,7 +53,10 @@ changelog: - '^docs:' - '^test:' gomod: - proxy: true + # proxy must be disabled: it re-fetches modules from the Go proxy in a clean + # directory, which does not honor the replace directive in go.mod + # (replace github.com/pion/dtls/v3 => ... pinning v3.1.2 for gst whipsink compat) + proxy: false mod: mod checksum: name_template: 'checksums.txt' diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7a77413..233153522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,44 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.13.1] - 2026-06-08 + +### Fixed + +- ci: disable goreleaser gomod proxy, incompatible with go.mod replace directive + +## [1.13.0] - 2026-06-08 + +## ATTENTION: This release removes backwards compatibility for TURN authentication without TTL. Please refer to change log of [1.12.0](https://github.com/livekit/livekit/releases/tag/v1.12.0) for details on changes to TURN authentication and permissions handling. It was removed in https://github.com/livekit/livekit/pull/4539. + +### Added + +- rtc: report participant kind code and details (#4534) +- Add IsIntentionalDisconnect (#4537) +- rtc: emit per-data-track bytes via BytesTrackStats (#4540) +- Start tracking Twirp method request latency in prometheus too, not just in logs (#4545) +- Config documentation for advertise_internal_ip and skip_external_ip_validation (#4552, #4554, #4563) +- Metrics for participant active, i. e. fully established. (#4557) +- turn: allow for providing secret via file (#4564) + +### Changed + +- Always enable rtx codec (#4533) +- Don't require media sections when joining (#4535) +- Use NACKQuueInterface type. (#4538) +- Remove backwards compatibility support for TURN auth. (#4539) +- telemetry: split webhook-processed hook out of NewTelemetryService (#4548) +- rtc: prevent duration reporting for inactive participants (#4550) +- rtc: report participant session end time on room move (#4561) +- Update mediatransportutil to get ICE candidate timeout config (#4572) + +### Fixed + +- Check Less and LessEq in version compare. (#4532) +- fix sip error categorization (#4528) +- handle nil clientInfo (#4546, #4547) +- Prevent panic from nil(illegal) syncState.Subscriptions message (#4560) + ## [1.12.0] - 2026-05-16 ## ATTENTION: This release introduces important changes to how TURN authentication and permissions are handled. These changes make the system more secure. This release maintains backwards compatibility. However, backwards compatibility will be removed in the next release. So, please plan accordingly. diff --git a/cmd/server/main.go b/cmd/server/main.go index ec2c0bf75..dde5b0adc 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -259,6 +259,10 @@ func startServer(ctx context.Context, c *cli.Command) error { return err } + if err = conf.LoadTURNSecrets(); err != nil { + return err + } + if cpuProfile := c.String("cpuprofile"); cpuProfile != "" { if f, err := os.Create(cpuProfile); err != nil { return err diff --git a/config-sample.yaml b/config-sample.yaml index 8b5891fdf..e4fd969c2 100644 --- a/config-sample.yaml +++ b/config-sample.yaml @@ -68,11 +68,11 @@ rtc: # # when set to true, advertises both mapped external and internal IPs to clients as server candidates. # # useful when clients connect from both private and public networks. # # works only when `use_external_ip` is set to true. + # # when both this and `external_ip_only` are set, SFU advertises all private IPs with their mapped external IPs and skips + # # private IPs that do not have a mapped external IP. # advertise_internal_ip: true # # when set to true, SFU skips external IP accessibility checks via self-ping. # # enable this when SFU is behind NAT that does not allow self-ping. only works when `use_external_ip` is set to true. - # # when both this and `external_ip_only` are set, SFU advertises all private IPs with their mapped external IPs and skips - # # private IPs that do not have a mapped external IP. # skip_external_ip_validation: true # # there are cases where the public IP determined via STUN is not the correct one # # in such cases, use this setting to set the public IP of the node @@ -98,6 +98,9 @@ rtc: # protocol: tls # # Shared secret for TURN server authentication # secret: "" + # # Path for file containing shared secret for TURN server authentication + # # When both secret and secret_file are set, secret takes precedence + # secret_file: "/var/lib/coturn/secret" # ttl: 14400 # seconds # # Insecure username/password authentication # username: "" @@ -111,6 +114,12 @@ rtc: # allow_pause: true # # allows automatic connection fallback to TCP and TURN/TLS (if configured) when UDP has been unstable, default true # allow_tcp_fallback: true + # # signaling RTT (in milliseconds) below which ICE/TCP is attempted on a UDP failure; at or above it, + # # supporting clients fall back directly to TURN/TLS. 0 (default) disables the check (ICE/TCP always tried). + # # a positive value also enables allow_udp_unstable_fallback. + # tcp_fallback_rtt_threshold: 0 + # # migrate an established-but-lossy UDP connection to ICE/TCP or TURN/TLS. requires tcp_fallback_rtt_threshold > 0, default false + # allow_udp_unstable_fallback: false # # number of packets to buffer in the SFU for video, defaults to 500 # packet_buffer_size_video: 500 # # number of packets to buffer in the SFU for audio, defaults to 200 @@ -161,6 +170,11 @@ rtc: # when enabled, LiveKit will expose prometheus metrics on :6789/metrics # prometheus_port: 6789 +# expose /debug/pprof (and /debug/goroutine, /debug/rooms) on a dedicated port, +# separate from the public signalling port. only enabled when port is set. +# debug_handler_port: +# port: 7070 + # API key / secret pairs. # Keys are used for JWT authentication, server APIs would require a keypair in order to generate access tokens # and make calls to the server diff --git a/go.mod b/go.mod index 84b2d1426..24632413c 100644 --- a/go.mod +++ b/go.mod @@ -8,52 +8,53 @@ require ( github.com/dennwc/iters v1.2.2 github.com/dustin/go-humanize v1.0.1 github.com/elliotchance/orderedmap/v3 v3.1.0 - github.com/florianl/go-tc v0.4.7 + github.com/florianl/go-tc v0.4.8 github.com/frostbyte73/core v0.1.1 github.com/gammazero/deque v1.2.1 github.com/gammazero/workerpool v1.2.1 github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 github.com/gorilla/websocket v1.5.3 - github.com/hashicorp/go-version v1.8.0 + github.com/hashicorp/go-version v1.9.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/jellydator/ttlcache/v3 v3.4.0 github.com/jxskiss/base62 v1.1.0 github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 - github.com/livekit/mediatransportutil v0.0.0-20260601042523-13d536af56d9 - github.com/livekit/protocol v1.45.9-0.20260519061926-8381f2180c45 - github.com/livekit/psrpc v0.7.1 + github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0 + github.com/livekit/protocol v1.46.7-0.20260611165352-04a0fe5b5051 + github.com/livekit/psrpc v0.7.2 github.com/mackerelio/go-osstat v0.2.7 - github.com/magefile/mage v1.17.0 + github.com/magefile/mage v1.17.2 github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2 github.com/mitchellh/go-homedir v1.1.0 + github.com/moby/moby/client v0.4.1 github.com/olekukonko/tablewriter v1.1.4 - github.com/ory/dockertest/v3 v3.12.0 + github.com/ory/dockertest/v4 v4.0.0 github.com/pion/datachannel v1.6.0 - github.com/pion/dtls/v3 v3.1.2 - github.com/pion/ice/v4 v4.2.6 - github.com/pion/interceptor v0.1.44 + github.com/pion/dtls/v3 v3.1.4 + github.com/pion/ice/v4 v4.2.7 + github.com/pion/interceptor v0.1.45 github.com/pion/rtcp v1.2.16 - github.com/pion/rtp v1.10.1 + github.com/pion/rtp v1.10.2 github.com/pion/sctp v1.9.5 github.com/pion/sdp/v3 v3.0.18 - github.com/pion/transport/v4 v4.0.1 - github.com/pion/turn/v5 v5.0.4 + github.com/pion/transport/v4 v4.0.2 + github.com/pion/turn/v5 v5.0.8 github.com/pion/webrtc/v4 v4.2.11 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.23.2 - github.com/redis/go-redis/v9 v9.18.0 + github.com/redis/go-redis/v9 v9.20.0 github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.11.1 github.com/thoas/go-funk v0.9.3 github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e github.com/twitchtv/twirp v8.1.3+incompatible - github.com/ua-parser/uap-go v0.0.0-20251207011819-db9adb27a0b8 + github.com/ua-parser/uap-go v0.0.0-20260529044130-17c35e68e58c github.com/urfave/negroni/v3 v3.1.1 go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 - go.uber.org/zap v1.27.1 - golang.org/x/mod v0.34.0 + go.uber.org/zap v1.28.0 + golang.org/x/mod v0.36.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 @@ -61,107 +62,96 @@ require ( require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/clipperhouse/displaywidth v0.10.0 // indirect - github.com/clipperhouse/uax29/v2 v2.6.0 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/cilium/ebpf v0.16.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-jose/go-jose/v3 v3.0.5 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/moby/sys/user v0.3.0 // indirect - github.com/nyaruka/phonenumbers v1.6.5 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/nyaruka/phonenumbers v1.8.0 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.2.0 // indirect - github.com/olekukonko/ll v0.1.6 // indirect + github.com/olekukonko/errors v1.3.0 // indirect + github.com/olekukonko/ll v0.1.8 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect golang.org/x/time v0.15.0 // indirect ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 // indirect - buf.build/go/protovalidate v1.1.3 // indirect - buf.build/go/protoyaml v0.6.0 // indirect - cel.dev/expr v0.25.1 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect + buf.build/go/protovalidate v1.2.0 // indirect + buf.build/go/protoyaml v0.7.0 // indirect + cel.dev/expr v0.25.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/continuity v0.4.5 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/cli v27.4.1+incompatible // indirect - github.com/docker/docker v27.1.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.5 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/cel-go v0.27.0 // indirect + github.com/google/cel-go v0.28.1 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/subcommands v1.2.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/compress v1.18.6 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lithammer/shortuuid/v4 v4.2.0 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/mdlayher/netlink v1.7.1 // indirect - github.com/mdlayher/socket v0.4.0 // indirect + github.com/mattn/go-runewidth v0.0.24 // indirect + github.com/mdlayher/netlink v1.11.2 // indirect + github.com/mdlayher/socket v0.6.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nats.go v1.50.0 // indirect - github.com/nats-io/nkeys v0.4.15 // indirect + github.com/nats-io/nats.go v1.52.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.2.3 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/srtp/v3 v3.0.10 // indirect - github.com/pion/stun/v3 v3.1.2 - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pion/srtp/v3 v3.0.11 // indirect + github.com/pion/stun/v3 v3.1.4 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/urfave/cli/v3 v3.8.0 + github.com/prometheus/common v0.68.1 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/urfave/cli/v3 v3.9.0 github.com/wlynxg/anet v0.0.5 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/net v0.53.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.43.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect - google.golang.org/grpc v1.80.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.45.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect ) replace github.com/livekit/protocol => ../protocol diff --git a/go.sum b/go.sum index df48857b1..66a2eb201 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,13 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= -buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE= -buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE= -buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w= -buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q= -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0= +buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4= +buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= +buf.build/go/protoyaml v0.7.0/go.mod h1:+a0cavd0uMvirb87xdu2ZMMmjlIQoiH/N2Ich5MGSQ0= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= @@ -30,8 +22,6 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -41,46 +31,45 @@ github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2u github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= -github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= -github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= -github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= -github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= -github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM= github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dennwc/iters v1.2.2 h1:XH2/Etihiy9ZvPOVCR+icQXeYlhbvS7k0qro4x/2qQo= github.com/dennwc/iters v1.2.2/go.mod h1:M9KuuMBeyEXYTmB7EnI9SCyALFCmPWOIxn5W1L0CjGg= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= -github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/florianl/go-tc v0.4.7 h1:Ysai5TIx4PgOzqI/1cse/pquOFCEkWofKtc/EPumfrg= -github.com/florianl/go-tc v0.4.7/go.mod h1:Fdz6eHitQZwylSvpAW3y9R9cUrnS/zinuAdjJpD7XqY= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/florianl/go-tc v0.4.8 h1:hgmakUX1Nm0Ba1I0ZkbUl9CH6HbRwqSiwipnpmYp3Es= +github.com/florianl/go-tc v0.4.8/go.mod h1:B8GeOEnmrbOnxZtaCvsYJcgIzzmM8c/AIhtfCZsDj3Q= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frostbyte73/core v0.1.1 h1:ChhJOR7bAKOCPbA+lqDLE2cGKlCG5JXsDvvQr4YaJIA= github.com/frostbyte73/core v0.1.1/go.mod h1:mhfOtR+xWAvwXiwor7jnqPMnu4fxbv1F2MwZ0BEpzZo= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ= github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g= github.com/gammazero/workerpool v1.2.1 h1:MEDvUJsNYGuCvl1RwIXNKu2YtQtHqCSF9XWF04N7lqs= @@ -92,16 +81,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/cel-go v0.28.1 h1:YWIwi77J4xIsYUwAF/iIuS6haffzIHS8yWI8glSbLWM= +github.com/google/cel-go v0.28.1/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -114,8 +99,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -130,10 +113,10 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -156,10 +139,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa79 github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs= github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= -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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= -github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -173,26 +154,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c= github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= -github.com/livekit/mediatransportutil v0.0.0-20260601042523-13d536af56d9 h1:kFXGyfVN9c2b66e6Q87evOvKrJE5u9e1Gd28gtjSLPI= -github.com/livekit/mediatransportutil v0.0.0-20260601042523-13d536af56d9/go.mod h1:KIBQV035+uwqfYvKPUh+7H3bXtaYO0TXhcxLosbhJ9A= -github.com/livekit/psrpc v0.7.1 h1:ms37az0QTD3UXIWuUC5D/SkmKOlRMVRsI261eBWu/Vw= -github.com/livekit/psrpc v0.7.1/go.mod h1:bZ4iHFQptTkbPnB0LasvRNu/OBYXEu1NA6O5BMFo9kk= +github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0 h1:XHNNzebIKZRkLimla/hFGrAIX5EMWHctrgt3hLw7s+I= +github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0/go.mod h1:o8CFmAdrVwzJNOCsQCLUzXRjokkufNshnQHOe4fRaqU= +github.com/livekit/psrpc v0.7.2 h1:6oZ+NODJ2pLyaT6VqDq1F4Qc/3TpDUSpyphj/P9MhQc= +github.com/livekit/psrpc v0.7.2/go.mod h1:rAI+m2+/cb4x9RXhLRtUx5ZwdfjjXOl4zi46IjEetaw= github.com/mackerelio/go-osstat v0.2.7 h1:TCavZi10wF49bT6iQZ9eT2keGZQpC69MTDfdJej5e94= github.com/mackerelio/go-osstat v0.2.7/go.mod h1:dwpYh5pIPmvk+IEwBKNIWRFMB92mrC08CmXOhDC7nQk= -github.com/magefile/mage v1.17.0 h1:dS4tkq997Ism03akafC8509iqDjeE7TNTexI25Y7sXM= -github.com/magefile/mage v1.17.0/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= +github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= +github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2 h1:V23nK2R2B63g2GhygF9zVGpnigmhvoZoH8d0hrZwMGY= github.com/maxbrunsfeld/counterfeiter/v6 v6.12.2/go.mod h1:Mr897yU9FmyKaQDPtRlVKibrjz40XXyOHUfyZBPSyZU= github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= @@ -207,57 +186,55 @@ github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnN github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q= -github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= -github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= -github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= +github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU= +github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI= +github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA= github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= -github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= -github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= +github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= +github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= +github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/nats.go v1.50.0 h1:5zAeQrTvyrKrWLJ0fu02W3br8ym57qf7csDzgLOpcds= -github.com/nats-io/nats.go v1.50.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= -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/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc= +github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= 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/nyaruka/phonenumbers v1.6.5 h1:aBCaUhfpRA7hU6fsXk+p7KF1aNx4nQlq9hGeo2qdFg8= -github.com/nyaruka/phonenumbers v1.6.5/go.mod h1:7gjs+Lchqm49adhAKB5cdcng5ZXgt6x7Jgvi0ZorUtU= +github.com/nyaruka/phonenumbers v1.8.0 h1:TrXNJmbwcAHajzDqin3mLWw57vqLUA6ZjVdeNds0heQ= +github.com/nyaruka/phonenumbers v1.8.0/go.mod h1:fsKPJ70O9JetEA4ggnJadYTFWwtGPvu/lETTXNXq6Cs= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= -github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= -github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= +github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U= +github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= +github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80= -github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= -github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= -github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/ory/dockertest/v4 v4.0.0 h1:i19aFsO/VXE0VrMk4ifnKW4G/KIJ93PCjLOslxXoPME= +github.com/ory/dockertest/v4 v4.0.0/go.mod h1:b5Ofu8VIxWNhXFvQcLu17pRNQdoUBKtXBW74G4Ygzx8= github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= -github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= -github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= -github.com/pion/ice/v4 v4.2.6 h1:17SKPTupOmumCMAQ4ntf8rv6q6XEfVzuz1gSFrnhzPE= -github.com/pion/ice/v4 v4.2.6/go.mod h1:tmp90fBKpZhQDkHkp/QJb+Gn8vhMxuAcMq7PfhQWQHE= -github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I= -github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY= +github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY= +github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc= +github.com/pion/ice/v4 v4.2.7 h1:zDEbC6MiEdhQpF8TxBOTws+NU6ZgGpveHrQq4Lc1kao= +github.com/pion/ice/v4 v4.2.7/go.mod h1:9SNPaq0c7El/ki8leJzyCkK10zsskprR3zTNbO3monY= +github.com/pion/interceptor v0.1.45 h1:6PUo/5829bIfRFIPPJQzuDn8EjxRTSB/CSD7QVCOaqo= +github.com/pion/interceptor v0.1.45/go.mod h1:gNDYM/uFKcLe/B3gS2/7+aw6z+RDiMy2qKTnF1LO31w= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= @@ -266,57 +243,55 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= -github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA= -github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo= +github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk= github.com/pion/sctp v1.9.5 h1:QoSFB/drmAsmSeSFNQNI3xx010nW4HsycCZckRVWWag= github.com/pion/sctp v1.9.5/go.mod h1:N20Dq6LY+JvJDAh9VVh1JELngb2rQ8dPgds5yBWiPgw= github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= -github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ= -github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M= -github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= -github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= +github.com/pion/srtp/v3 v3.0.11 h1:GiESUr54/K4UuPigfq/CvWUed80JenQAHXn0C2MQQIQ= +github.com/pion/srtp/v3 v3.0.11/go.mod h1:EeZOi/sd6glM1EXapg051gdNWO9yWT1YSsgQ4SlJkns= +github.com/pion/stun/v3 v3.1.4 h1:/7ZL0j0dmLroKOq4GfkyKQ6asByYqntwyHSp5sYLcGY= +github.com/pion/stun/v3 v3.1.4/go.mod h1:ET7PFiXo1nrD2ZNVpbEHDuT0kCPVXhKmyWdiePNMw/U= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= -github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= -github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= +github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk= +github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= -github.com/pion/turn/v5 v5.0.4 h1:xKAnP1b5eCnjFPd55OgxkqIVoyzbHKZa06SxZ3fopXQ= -github.com/pion/turn/v5 v5.0.4/go.mod h1:zbPsMp+fIVhKt5uWu2jcjk88FcoRbGMBSzVsaTuhcmM= +github.com/pion/turn/v5 v5.0.8 h1:pZUCtmwWCMkrRKqh/8pL3WoGADXBe0/lOPkN7oqFjK8= +github.com/pion/turn/v5 v5.0.8/go.mod h1:1VwvxElZaOdJU0liJ/WUSm/Tsh+n2OxS5ISSDxgOWxU= github.com/pion/webrtc/v4 v4.2.11 h1:QUX1QZKlNIn4O7U5JxLPGP0sV5RTncZkzu9SPR3jVNU= github.com/pion/webrtc/v4 v4.2.11/go.mod h1:s/rAiyy77GyRFrZMx+Ls6aua26dIBPudH8/ZHYbIRWY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= +github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= -github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= +github.com/redis/go-redis/v9 v9.20.0 h1:WnQYxLkgO2xiXTCJY0ldIiI8dNqCDlQAG+AtaH7a2a0= +github.com/redis/go-redis/v9 v9.20.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= +github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -327,23 +302,14 @@ github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4n github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY= github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/ua-parser/uap-go v0.0.0-20251207011819-db9adb27a0b8 h1:yS0rzVnj7Z/ZeHzvv5erQbO2b8gyTL4CeMNodl9SJMQ= -github.com/ua-parser/uap-go v0.0.0-20251207011819-db9adb27a0b8/go.mod h1:gwANdYmo9R8LLwGnyDFWK2PMsaXXX2HhAvCnb/UhZsM= -github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= -github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/ua-parser/uap-go v0.0.0-20260529044130-17c35e68e58c h1:XbG4n3OWA1PcRTpbBA22E2ChPLvJCuwYRXO12tIyVL0= +github.com/ua-parser/uap-go v0.0.0-20260529044130-17c35e68e58c/go.mod h1:gwANdYmo9R8LLwGnyDFWK2PMsaXXX2HhAvCnb/UhZsM= +github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= +github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/negroni/v3 v3.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw= github.com/urfave/negroni/v3 v3.1.1/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -351,20 +317,22 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -373,56 +341,50 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= +golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= @@ -446,20 +408,16 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -472,30 +430,27 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 h1:yOzSCGPx+cp5VO7IxvZ9SBFF7j1tZVcNtlHR2iYKtVo= -google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:Q9HWtNeE7tM9npdIsEvqXj1QJIvVoeAV3rtXtS715Cw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -504,10 +459,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/pkg/config/config.go b/pkg/config/config.go index 11391cf04..05d1dbe30 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -49,8 +49,9 @@ const ( ) var ( - ErrKeyFileIncorrectPermission = errors.New("key file others permissions must be set to 0") - ErrKeysNotSet = errors.New("one of key-file or keys must be provided") + ErrKeyFileIncorrectPermission = errors.New("key file others permissions must be set to 0") + ErrTURNSecretFileIncorrectPermission = errors.New("turn secret file others permissions must be set to 0") + ErrKeysNotSet = errors.New("one of key-file or keys must be provided") ) type Config struct { @@ -59,6 +60,7 @@ type Config struct { // PrometheusPort is deprecated PrometheusPort uint32 `yaml:"prometheus_port,omitempty"` Prometheus PrometheusConfig `yaml:"prometheus,omitempty"` + DebugHandler DebugHandlerConfig `yaml:"debug_handler,omitempty"` RTC RTCConfig `yaml:"rtc,omitempty"` Redis redisLiveKit.RedisConfig `yaml:"redis,omitempty"` Audio sfu.AudioConfig `yaml:"audio,omitempty"` @@ -117,6 +119,18 @@ type RTCConfig struct { // allow TCP and TURN/TLS fallback AllowTCPFallback *bool `yaml:"allow_tcp_fallback,omitempty"` + // Signaling RTT threshold (in milliseconds) governing ICE/TCP fallback. On a UDP + // failure, ICE/TCP is attempted only while the measured signaling RTT is below this + // value; at or above it, supporting clients fall back directly to TURN/TLS. When 0 + // (the default), the RTT check is disabled and ICE/TCP is always attempted (for + // clients that support it). A positive value also gates allow_udp_unstable_fallback. + TCPFallbackRTTThreshold int `yaml:"tcp_fallback_rtt_threshold,omitempty"` + + // When enabled, an established UDP connection reporting sustained high packet loss is + // migrated to ICE/TCP or TURN/TLS. Requires tcp_fallback_rtt_threshold to be set + // (> 0). Disabled by default. + AllowUDPUnstableFallback bool `yaml:"allow_udp_unstable_fallback,omitempty"` + // force a reconnect on a publication error ReconnectOnPublicationError *bool `yaml:"reconnect_on_publication_error,omitempty"` @@ -151,6 +165,8 @@ type TURNServer struct { // Secret is used for TURN static auth secrets mechanism. When provided, // dynamic credentials are generated using HMAC-SHA1 instead of static Username/Credential Secret string `yaml:"secret,omitempty"` + // File containing the secret + SecretFile string `yaml:"secret_file,omitempty"` // TTL is the time-to-live in seconds for generated credentials when using Secret. // Defaults to 14400 seconds (4 hours) if not specified TTL int `yaml:"ttl,omitempty"` @@ -370,6 +386,10 @@ type PrometheusConfig struct { Password string `yaml:"password,omitempty"` } +type DebugHandlerConfig struct { + Port uint32 `yaml:"port,omitempty"` +} + type ForwardStatsConfig struct { SummaryInterval time.Duration `yaml:"summary_interval,omitempty"` ReportInterval time.Duration `yaml:"report_interval,omitempty"` @@ -681,6 +701,33 @@ func (conf *Config) ValidateKeys() error { return nil } +func (conf *Config) LoadTURNSecrets() error { + var otherFilter os.FileMode = 0o007 + for i, s := range conf.RTC.TURNServers { + if s.SecretFile == "" { + continue + } + if s.Secret != "" { + logger.Warnw("both secret and secret_file are set for TURN server, the hardcoded secret will be used", nil, + "host", s.Host, "port", s.Port) + continue + } + st, err := os.Stat(s.SecretFile) + if err != nil { + return err + } + if st.Mode().Perm()&otherFilter != 0o000 { + return ErrTURNSecretFileIncorrectPermission + } + data, err := os.ReadFile(s.SecretFile) + if err != nil { + return fmt.Errorf("reading turn secret file %q: %w", s.SecretFile, err) + } + conf.RTC.TURNServers[i].Secret = strings.TrimSpace(string(data)) + } + return nil +} + func GenerateCLIFlags(existingFlags []cli.Flag, hidden bool) ([]cli.Flag, error) { defaultConfig := &DefaultConfig flags := make([]cli.Flag, 0) diff --git a/pkg/routing/interfaces.go b/pkg/routing/interfaces.go index 6b2e1b6ce..08395efa0 100644 --- a/pkg/routing/interfaces.go +++ b/pkg/routing/interfaces.go @@ -17,6 +17,7 @@ package routing import ( "context" "encoding/json" + "time" "github.com/redis/go-redis/v9" "go.uber.org/atomic" @@ -192,6 +193,7 @@ type ParticipantInit struct { AutoSubscribeDataTrack *bool Client *livekit.ClientInfo Grants *auth.ClaimGrants + TokenExpiresAt time.Time Region string AdaptiveStream bool ID livekit.ParticipantID @@ -224,6 +226,9 @@ func (pi *ParticipantInit) MarshalLogObject(e zapcore.ObjectEncoder) error { logBoolPtr("AutoSubscribeDataTrack", pi.AutoSubscribeDataTrack) e.AddObject("Client", logger.Proto(utils.ClientInfoWithoutAddress(pi.Client))) e.AddObject("Grants", pi.Grants) + if !pi.TokenExpiresAt.IsZero() { + e.AddTime("TokenExpiresAt", pi.TokenExpiresAt) + } e.AddString("Region", pi.Region) logBoolPtr("AdaptiveStream", &pi.AdaptiveStream) e.AddString("ID", string(pi.ID)) @@ -243,6 +248,11 @@ func (pi *ParticipantInit) ToStartSession(roomName livekit.RoomName, connectionI return nil, err } + var tokenExpiresAt int64 + if !pi.TokenExpiresAt.IsZero() { + tokenExpiresAt = pi.TokenExpiresAt.Unix() + } + ss := &livekit.StartSession{ RoomName: string(roomName), Identity: string(pi.Identity), @@ -253,6 +263,7 @@ func (pi *ParticipantInit) ToStartSession(roomName livekit.RoomName, connectionI AutoSubscribe: pi.AutoSubscribe, Client: pi.Client, GrantsJson: string(claims), + TokenExpiresAt: tokenExpiresAt, AdaptiveStream: pi.AdaptiveStream, ParticipantId: string(pi.ID), DisableIceLite: pi.DisableICELite, @@ -279,6 +290,10 @@ func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (* if err := json.Unmarshal([]byte(ss.GrantsJson), claims); err != nil { return nil, err } + var tokenExpiresAt time.Time + if ss.TokenExpiresAt > 0 { + tokenExpiresAt = time.Unix(ss.TokenExpiresAt, 0) + } pi := &ParticipantInit{ Identity: livekit.ParticipantIdentity(ss.Identity), @@ -288,6 +303,7 @@ func ParticipantInitFromStartSession(ss *livekit.StartSession, region string) (* Client: ss.Client, AutoSubscribe: ss.AutoSubscribe, Grants: claims, + TokenExpiresAt: tokenExpiresAt, Region: region, AdaptiveStream: ss.AdaptiveStream, ID: livekit.ParticipantID(ss.ParticipantId), diff --git a/pkg/routing/nodestats.go b/pkg/routing/nodestats.go index 999ae9cb3..ac16bb0ef 100644 --- a/pkg/routing/nodestats.go +++ b/pkg/routing/nodestats.go @@ -20,6 +20,7 @@ import ( "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" + "github.com/livekit/protocol/utils" "github.com/livekit/livekit-server/pkg/config" "github.com/livekit/livekit-server/pkg/telemetry/prometheus" @@ -29,7 +30,7 @@ type NodeStats struct { config config.NodeStatsConfig startedAt int64 - lock sync.Mutex + lock sync.RWMutex statsHistory []*livekit.NodeStats statsHistoryWritePtr int } @@ -80,3 +81,11 @@ func (n *NodeStats) UpdateAndGetNodeStats() (*livekit.NodeStats, error) { n.statsHistoryWritePtr = (n.statsHistoryWritePtr + 1) % len(n.statsHistory) return stats, nil } + +func (n *NodeStats) GetLatestNodeStats() *livekit.NodeStats { + n.lock.RLock() + defer n.lock.RUnlock() + + latestPtr := (n.statsHistoryWritePtr - 1 + len(n.statsHistory)) % len(n.statsHistory) + return utils.CloneProto(n.statsHistory[latestPtr]) +} diff --git a/pkg/rtc/datadowntrack.go b/pkg/rtc/datadowntrack.go index 89c069f1b..45f5e19a8 100644 --- a/pkg/rtc/datadowntrack.go +++ b/pkg/rtc/datadowntrack.go @@ -16,6 +16,7 @@ package rtc import ( "fmt" + "sync" "time" "github.com/livekit/livekit-server/pkg/rtc/datatrack" @@ -40,6 +41,10 @@ type DataDownTrack struct { params DataDownTrackParams logger logger.Logger createdAt int64 + + lock sync.Mutex + closed bool + onClose func() } func NewDataDownTrack(params DataDownTrackParams) (*DataDownTrack, error) { @@ -59,11 +64,34 @@ func NewDataDownTrack(params DataDownTrackParams) (*DataDownTrack, error) { } func (d *DataDownTrack) Close() { + d.lock.Lock() + onClose := d.onClose + if d.closed { + d.lock.Unlock() + return + } + d.closed = true + d.lock.Unlock() + d.logger.Infow("closing data down track") if d.params.BytesTrackStats != nil { d.params.BytesTrackStats.Stop() } d.params.PublishDataTrack.DeleteDataDownTrack(d.SubscriberID()) + + if onClose != nil { + onClose() + } +} + +func (d *DataDownTrack) OnClose(fn func()) { + d.lock.Lock() + d.onClose = fn + closed := d.closed + d.lock.Unlock() + if closed && fn != nil { + fn() + } } func (d *DataDownTrack) Handle() uint16 { diff --git a/pkg/rtc/datatrack.go b/pkg/rtc/datatrack.go index 1e8c4d55c..fd22f6258 100644 --- a/pkg/rtc/datatrack.go +++ b/pkg/rtc/datatrack.go @@ -16,9 +16,11 @@ package rtc import ( "errors" + "slices" "sync" "github.com/frostbyte73/core" + "github.com/livekit/livekit-server/pkg/rtc/datatrack" "github.com/livekit/livekit-server/pkg/rtc/types" sfuutils "github.com/livekit/livekit-server/pkg/sfu/utils" @@ -40,6 +42,11 @@ type DataTrackParams struct { BytesTrackStats *BytesTrackStats } +type subscribedDataTrack struct { + subscriber types.LocalParticipant + dataDownTrack types.DataDownTrack +} + type DataTrack struct { params DataTrackParams @@ -47,7 +54,7 @@ type DataTrack struct { lock sync.Mutex dti *livekit.DataTrackInfo - subscribedTracks map[livekit.ParticipantID]types.DataDownTrack + subscribedTracks map[livekit.ParticipantID]subscribedDataTrack downTrackSpreader *sfuutils.DownTrackSpreader[types.DataTrackSender] @@ -60,7 +67,7 @@ func NewDataTrack(params DataTrackParams, dti *livekit.DataTrackInfo) *DataTrack d := &DataTrack{ params: params, dti: dti, - subscribedTracks: make(map[livekit.ParticipantID]types.DataDownTrack), + subscribedTracks: make(map[livekit.ParticipantID]subscribedDataTrack), } d.logger = params.Logger.WithValues("name", d.Name(), "handle", dti.PubHandle) d.downTrackSpreader = sfuutils.NewDownTrackSpreader[types.DataTrackSender](sfuutils.DownTrackSpreaderParams{ @@ -136,18 +143,21 @@ func (d *DataTrack) AddSubscriber(sub types.LocalParticipant) (types.DataDownTra return nil, err } - d.subscribedTracks[sub.ID()] = dataDownTrack + d.subscribedTracks[sub.ID()] = subscribedDataTrack{ + subscriber: sub, + dataDownTrack: dataDownTrack, + } return dataDownTrack, nil } func (d *DataTrack) RemoveSubscriber(subID livekit.ParticipantID) { d.lock.Lock() - dataDownTrack, ok := d.subscribedTracks[subID] + subscribedTrack, ok := d.subscribedTracks[subID] delete(d.subscribedTracks, subID) d.lock.Unlock() if ok { - dataDownTrack.Close() + subscribedTrack.dataDownTrack.Close() } } @@ -159,6 +169,34 @@ func (d *DataTrack) IsSubscriber(subID livekit.ParticipantID) bool { return ok } +func (d *DataTrack) RevokeDisallowedSubscribers(allowedSubscriberIdentities []livekit.ParticipantIdentity) []livekit.ParticipantIdentity { + var revokedSubscriberIdentities []livekit.ParticipantIdentity + + d.lock.Lock() + disallowed := make(map[livekit.ParticipantID]livekit.ParticipantIdentity) + for subID, subscribedTrack := range d.subscribedTracks { + if IsParticipantExemptFromTrackPermissionsRestrictions(subscribedTrack.subscriber) { + continue + } + + if !slices.Contains(allowedSubscriberIdentities, subscribedTrack.subscriber.Identity()) { + disallowed[subID] = subscribedTrack.subscriber.Identity() + } + } + d.lock.Unlock() + + for subID, subIdentity := range disallowed { + d.logger.Infow("revoking data track subscription", + "subscriber", subIdentity, + "subscriberID", subID, + ) + d.RemoveSubscriber(subID) + revokedSubscriberIdentities = append(revokedSubscriberIdentities, subIdentity) + } + + return revokedSubscriberIdentities +} + func (d *DataTrack) AddDataDownTrack(dts types.DataTrackSender) error { if d.closed.IsBroken() { return errReceiverClosed diff --git a/pkg/rtc/datatrack_test.go b/pkg/rtc/datatrack_test.go new file mode 100644 index 000000000..f2938a1f2 --- /dev/null +++ b/pkg/rtc/datatrack_test.go @@ -0,0 +1,71 @@ +// Copyright 2023 LiveKit, Inc. +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtc + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/livekit/livekit-server/pkg/rtc/types/typesfakes" + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "github.com/livekit/protocol/observability/roomobs" +) + +func newTestDataTrackSubscriber(id livekit.ParticipantID, identity livekit.ParticipantIdentity, isRecorder bool) *typesfakes.FakeLocalParticipant { + sub := &typesfakes.FakeLocalParticipant{} + sub.IDReturns(id) + sub.IdentityReturns(identity) + sub.IsRecorderReturns(isRecorder) + sub.GetLoggerReturns(logger.GetLogger()) + sub.GetReporterReturns(roomobs.NewNoopParticipantSessionReporter()) + sub.GetTelemetryListenerReturns(&typesfakes.FakeParticipantTelemetryListener{}) + return sub +} + +func TestDataTrackRevokeDisallowedSubscribers(t *testing.T) { + dt := NewDataTrack( + DataTrackParams{ + Logger: logger.GetLogger(), + ParticipantID: func() livekit.ParticipantID { return "pubID" }, + ParticipantIdentity: "pub", + }, + &livekit.DataTrackInfo{ + PubHandle: 1, + Sid: "DTR_test", + Name: "test", + }, + ) + defer dt.Close() + + allowed := newTestDataTrackSubscriber("allowedID", "allowed", false) + disallowed := newTestDataTrackSubscriber("disallowedID", "disallowed", false) + recorder := newTestDataTrackSubscriber("recorderID", "recorder", true) + + for _, sub := range []*typesfakes.FakeLocalParticipant{allowed, disallowed, recorder} { + _, err := dt.AddSubscriber(sub) + require.NoError(t, err) + require.True(t, dt.IsSubscriber(sub.ID())) + } + + revoked := dt.RevokeDisallowedSubscribers([]livekit.ParticipantIdentity{"allowed"}) + require.Equal(t, []livekit.ParticipantIdentity{"disallowed"}, revoked) + + // disallowed subscriber is removed, allowed and permission exempt (recorder) subscribers are kept + require.True(t, dt.IsSubscriber(allowed.ID())) + require.False(t, dt.IsSubscriber(disallowed.ID())) + require.True(t, dt.IsSubscriber(recorder.ID())) +} diff --git a/pkg/rtc/participant.go b/pkg/rtc/participant.go index 597d1a19d..57312c034 100644 --- a/pkg/rtc/participant.go +++ b/pkg/rtc/participant.go @@ -174,57 +174,58 @@ type ParticipantParams struct { PLIThrottleConfig sfu.PLIThrottleConfig CongestionControlConfig config.CongestionControlConfig // codecs that are enabled for this room - PublishEnabledCodecs []*livekit.Codec - SubscribeEnabledCodecs []*livekit.Codec - Logger logger.Logger - LoggerResolver logger.DeferredFieldResolver - Reporter roomobs.ParticipantSessionReporter - ReporterResolver roomobs.ParticipantReporterResolver - SimTracks map[uint32]interceptor.SimulcastTrackInfo - Grants *auth.ClaimGrants - InitialVersion uint32 - ClientConf *livekit.ClientConfiguration - ClientInfo ClientInfo - Region string - Migration bool - Reconnect bool - AdaptiveStream bool - AllowTCPFallback bool - TCPFallbackRTTThreshold int - AllowUDPUnstableFallback bool - TURNSEnabled bool - ParticipantListener types.LocalParticipantListener - ParticipantHelper types.LocalParticipantHelper - DisableSupervisor bool - ReconnectOnPublicationError bool - ReconnectOnSubscriptionError bool - ReconnectOnDataChannelError bool - VersionGenerator utils.TimedVersionGenerator - DisableDynacast bool - SubscriberAllowPause bool - SubscriptionLimitAudio int32 - SubscriptionLimitVideo int32 - PlayoutDelay *livekit.PlayoutDelay - SyncStreams bool - ForwardStats *sfu.ForwardStats - DisableSenderReportPassThrough bool - MetricConfig metric.MetricConfig - UseOneShotSignallingMode bool - EnableMetrics bool - DataChannelMaxBufferedAmount uint64 - DatachannelSlowThreshold int - DatachannelLossyTargetLatency time.Duration - FireOnTrackBySdp bool - DisableCodecRegression bool - LastPubReliableSeq uint32 - Country string - PreferVideoSizeFromMedia bool - UseSinglePeerConnection bool - EnableDataTracks bool - EnableRTPStreamRestartDetection bool - ForceBackupCodecPolicySimulcast bool - DisableTransceiverReuseForE2EE bool - EnableParticipantAsyncAttributes bool + PublishEnabledCodecs []*livekit.Codec + SubscribeEnabledCodecs []*livekit.Codec + Logger logger.Logger + LoggerResolver logger.DeferredFieldResolver + Reporter roomobs.ParticipantSessionReporter + ReporterResolver roomobs.ParticipantReporterResolver + SimTracks map[uint32]interceptor.SimulcastTrackInfo + Grants *auth.ClaimGrants + TokenExpiresAt time.Time + InitialVersion uint32 + ClientConf *livekit.ClientConfiguration + ClientInfo ClientInfo + Region string + Migration bool + Reconnect bool + AdaptiveStream bool + AllowTCPFallback bool + TCPFallbackRTTThreshold int + AllowUDPUnstableFallback bool + TURNSEnabled bool + ParticipantListener types.LocalParticipantListener + ParticipantHelper types.LocalParticipantHelper + DisableSupervisor bool + ReconnectOnPublicationError bool + ReconnectOnSubscriptionError bool + ReconnectOnDataChannelError bool + VersionGenerator utils.TimedVersionGenerator + DisableDynacast bool + SubscriberAllowPause bool + SubscriptionLimitAudio int32 + SubscriptionLimitVideo int32 + PlayoutDelay *livekit.PlayoutDelay + SyncStreams bool + ForwardStats *sfu.ForwardStats + DisableSenderReportPassThrough bool + MetricConfig metric.MetricConfig + UseOneShotSignallingMode bool + EnableMetrics bool + DataChannelMaxBufferedAmount uint64 + DatachannelSlowThreshold int + DatachannelLossyTargetLatency time.Duration + FireOnTrackBySdp bool + DisableCodecRegression bool + LastPubReliableSeq uint32 + Country string + PreferVideoSizeFromMedia bool + UseSinglePeerConnection bool + EnableDataTracks bool + EnableRTPStreamRestartDetection bool + ForceBackupCodecPolicySimulcast bool + DisableTransceiverReuseForE2EE bool + EnableParticipantDataBlobs bool } type ParticipantImpl struct { @@ -410,19 +411,23 @@ func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) { p.supervisor.OnPublicationError(p.onPublicationError) } + var timerStarted bool params.Reporter.RegisterFunc(func(ts time.Time, tx roomobs.ParticipantSessionTx) bool { - // Don't publish duration if participant never became active. Otherwise short-lived - // JOINING/JOINED -> DISCONNECTED transitions would still get rounded up to a - // minute by the session timer and inflate billed/reported duration. - if p.lastActiveAt.Load() == nil { - return !p.IsClosed() - } - if dts := p.disconnectedAt.Load(); dts != nil { ts = *dts tx.ReportEndTime(ts) } + // Don't publish duration if participant never became active. Otherwise short-lived + // JOINING/JOINED -> DISCONNECTED transitions would still get rounded up to a + // minute by the session timer and inflate billed/reported duration. + if lastActive := p.lastActiveAt.Load(); lastActive == nil { + return !p.IsClosed() + } else if !timerStarted { + timerStarted = true + p.params.SessionTimer.Reset(*lastActive) + } + tx.ReportKindCode(roomobs.ParticipantKindCode(p.Kind())) tx.ReportKindDetailsCodes(roomobs.ParticipantKindDetailsCodes(p.KindDetails())) @@ -787,6 +792,10 @@ func (p *ParticipantImpl) ClaimGrants() *auth.ClaimGrants { return p.grants.Load() } +func (p *ParticipantImpl) TokenExpiresAt() time.Time { + return p.params.TokenExpiresAt +} + func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermission) bool { if permission == nil { return false @@ -4075,6 +4084,7 @@ func (p *ParticipantImpl) MoveToRoom(params types.MoveToRoomParams) { p.telemetryGuard = &telemetry.ReferenceGuard{} p.lock.Unlock() + p.params.Reporter.ReportEndTime(time.Now()) p.params.LoggerResolver.Reset() p.params.ReporterResolver.Reset() p.setListener(params.Listener) diff --git a/pkg/rtc/participant_data_track.go b/pkg/rtc/participant_data_track.go index 829beaf63..dbda1ede0 100644 --- a/pkg/rtc/participant_data_track.go +++ b/pkg/rtc/participant_data_track.go @@ -16,6 +16,7 @@ package rtc import ( "github.com/livekit/livekit-server/pkg/rtc/datatrack" + "github.com/livekit/livekit-server/pkg/rtc/types" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils" @@ -156,6 +157,33 @@ func (p *ParticipantImpl) onReceivedDataTrackMessage(data []byte, arrivalTime in p.listener().OnDataTrackMessage(p, data, &packet) } +// wraps the promoted UpTrackManager.UpdateSubscriptionPermission to also revoke +// data track subscriptions that are no longer permitted +func (p *ParticipantImpl) UpdateSubscriptionPermission( + subscriptionPermission *livekit.SubscriptionPermission, + timedVersion utils.TimedVersion, + resolverBySid func(participantID livekit.ParticipantID) types.LocalParticipant, +) error { + if err := p.UpTrackManager.UpdateSubscriptionPermission(subscriptionPermission, timedVersion, resolverBySid); err != nil { + return err + } + + p.maybeRevokeDataTrackSubscriptions() + return nil +} + +func (p *ParticipantImpl) maybeRevokeDataTrackSubscriptions() { + for _, dt := range p.UpDataTrackManager.GetPublishedDataTracks() { + allowed := p.UpTrackManager.GetAllowedSubscribers(dt.ID()) + if allowed == nil { + // no restrictions + continue + } + + dt.RevokeDisallowedSubscribers(allowed) + } +} + func (p *ParticipantImpl) GetNextSubscribedDataTrackHandle() uint16 { p.lock.Lock() defer p.lock.Unlock() diff --git a/pkg/rtc/room.go b/pkg/rtc/room.go index d504d33ad..f9ea860d1 100644 --- a/pkg/rtc/room.go +++ b/pkg/rtc/room.go @@ -686,14 +686,14 @@ func (r *Room) onSyncState(participant types.LocalParticipant, state *livekit.Sy // synthesize a track setting for each disabled track, // can be set before adding subscriptions, // in fact it is done before so that setting can be updated immediately upon subscription. - for _, trackSid := range state.TrackSidsDisabled { + for _, trackSid := range state.GetTrackSidsDisabled() { participant.UpdateSubscribedTrackSettings(livekit.TrackID(trackSid), &livekit.UpdateTrackSettings{Disabled: true}) } participant.HandleUpdateSubscriptions( - livekit.StringsAsIDs[livekit.TrackID](state.Subscription.TrackSids), - state.Subscription.ParticipantTracks, - state.Subscription.Subscribe, + livekit.StringsAsIDs[livekit.TrackID](state.GetSubscription().GetTrackSids()), + state.GetSubscription().GetParticipantTracks(), + state.GetSubscription().GetSubscribe(), ) return nil } @@ -705,6 +705,9 @@ func (r *Room) onUpdateSubscriptionPermission(participant types.LocalParticipant for _, track := range participant.GetPublishedTracks() { r.trackManager.NotifyTrackChanged(track.ID()) } + for _, track := range participant.GetPublishedDataTracks() { + r.trackManager.NotifyTrackChanged(track.ID()) + } return nil } @@ -746,6 +749,13 @@ func (r *Room) ResolveDataTrackForSubscriber(sub types.LocalParticipant, trackID res.TrackRemovedNotifier = r.trackManager.GetOrCreateTrackRemoveNotifier(trackID) res.PublisherIdentity = info.PublisherIdentity res.PublisherID = info.PublisherID + + pub := r.GetParticipantByID(info.PublisherID) + // when publisher is not found, we will assume it doesn't have permission to access + if pub != nil { + res.HasPermission = IsParticipantExemptFromTrackPermissionsRestrictions(sub) || pub.HasPermission(trackID, sub.Identity()) + } + return res } diff --git a/pkg/rtc/subscriptionmanager.go b/pkg/rtc/subscriptionmanager.go index 640742bfd..d421d0805 100644 --- a/pkg/rtc/subscriptionmanager.go +++ b/pkg/rtc/subscriptionmanager.go @@ -612,8 +612,9 @@ func (m *SubscriptionManager) reconcileDataTrackSubscription(s *dataTrackSubscri s.recordAttempt(false) switch err { - case ErrNoSubscribePermission: + case ErrNoTrackPermission, ErrNoSubscribePermission: // these are errors that are outside of our control, so we'll keep trying + // - ErrNoTrackPermission: publisher did not grant subscriber permission, may change any moment // - ErrNoSubscribePermission: participant was not granted canSubscribe, may change any moment case ErrTrackNotFound: // source track was never published or closed @@ -1083,6 +1084,14 @@ func (m *SubscriptionManager) subscribeDataTrack(sub *dataTrackSubscription) err sub.setPublisher(res.PublisherIdentity, res.PublisherID) + permChanged := sub.setHasPermission(res.HasPermission) + if permChanged { + m.params.Participant.SendSubscriptionPermissionUpdate(sub.getPublisherID(), trackID, res.HasPermission) + } + if !res.HasPermission { + return ErrNoTrackPermission + } + dataDownTrack, err := dataTrack.AddSubscriber(m.params.Participant) if err != nil && !errors.Is(err, errAlreadySubscribed) { return err @@ -1091,6 +1100,9 @@ func (m *SubscriptionManager) subscribeDataTrack(sub *dataTrackSubscription) err sub.logger.Debugw("already subscribed to data track") } if err == nil && dataDownTrack != nil { // subTrack could be nil if already subscribed + dataDownTrack.OnClose(func() { + m.handleDataDownTrackClose(sub) + }) sub.setDataDownTrack(dataDownTrack) sub.logger.Debugw("subscribed to data track") } @@ -1111,11 +1123,27 @@ func (m *SubscriptionManager) unsubscribeDataTrack(s *dataTrackSubscription) err dataTrack := dataDownTrack.PublishDataTrack() dataTrack.RemoveSubscriber(s.subscriberID) + return nil +} + +// DataDownTrack closing is how the publisher signifies that the subscription is no longer fulfilled +// this could be due to a few reasons: +// - subscriber-initiated unsubscribe +// - data track was unpublished +// - publisher revoked permissions for the participant +func (m *SubscriptionManager) handleDataDownTrackClose(s *dataTrackSubscription) { + s.logger.Debugw("data down track closed") + + if s.getDataDownTrack() == nil { + return + } + s.setDataDownTrack(nil) s.setChangedNotifier(nil) s.setRemovedNotifier(nil) m.unmarkSubscribedTo(s.getPublisherID(), s.trackID) - return nil + m.notifyDataTrackSubscriberHandles() + m.queueReconcileDataTrack(s.trackID) } func (m *SubscriptionManager) notifyDataTrackSubscriberHandles() { @@ -1208,12 +1236,14 @@ type trackSubscription struct { trackID livekit.TrackID logger logger.Logger - lock sync.RWMutex - desired bool - publisherID livekit.ParticipantID - publisherIdentity livekit.ParticipantIdentity - changedNotifier types.ChangeNotifier - removedNotifier types.ChangeNotifier + lock sync.RWMutex + desired bool + publisherID livekit.ParticipantID + publisherIdentity livekit.ParticipantIdentity + changedNotifier types.ChangeNotifier + removedNotifier types.ChangeNotifier + hasPermissionInitialized bool + hasPermission bool numAttempts atomic.Int32 @@ -1225,6 +1255,31 @@ type trackSubscription struct { subscribeAt atomic.Pointer[time.Time] } +// set permission and return true if it has changed +func (s *trackSubscription) setHasPermission(perm bool) bool { + s.lock.Lock() + defer s.lock.Unlock() + if s.hasPermissionInitialized && s.hasPermission == perm { + return false + } + + s.hasPermissionInitialized = true + s.hasPermission = perm + if s.hasPermission { + // when permission is granted, reset the timer so it has sufficient time to reconcile + t := time.Now() + s.subStartedAt.Store(&t) + s.subscribeAt.Store(&t) + } + return true +} + +func (s *trackSubscription) getHasPermission() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.hasPermission +} + func (s *trackSubscription) setPublisher(publisherIdentity livekit.ParticipantIdentity, publisherID livekit.ParticipantID) { s.lock.Lock() defer s.lock.Unlock() @@ -1355,13 +1410,11 @@ func (s *trackSubscription) handleSourceTrackRemoved() { type mediaTrackSubscription struct { trackSubscription - settings *livekit.UpdateTrackSettings - hasPermissionInitialized bool - hasPermission bool - subscribedTrack types.SubscribedTrack - eventSent atomic.Bool - bound bool - kind atomic.Pointer[livekit.TrackType] + settings *livekit.UpdateTrackSettings + subscribedTrack types.SubscribedTrack + eventSent atomic.Bool + bound bool + kind atomic.Pointer[livekit.TrackType] succRecordCounter atomic.Int32 } @@ -1379,31 +1432,6 @@ func newMediaTrackSubscription(subscriberID livekit.ParticipantID, trackID livek return s } -// set permission and return true if it has changed -func (s *mediaTrackSubscription) setHasPermission(perm bool) bool { - s.lock.Lock() - defer s.lock.Unlock() - if s.hasPermissionInitialized && s.hasPermission == perm { - return false - } - - s.hasPermissionInitialized = true - s.hasPermission = perm - if s.hasPermission { - // when permission is granted, reset the timer so it has sufficient time to reconcile - t := time.Now() - s.subStartedAt.Store(&t) - s.subscribeAt.Store(&t) - } - return true -} - -func (s *mediaTrackSubscription) getHasPermission() bool { - s.lock.RLock() - defer s.lock.RUnlock() - return s.hasPermission -} - func (s *mediaTrackSubscription) setSubscribedTrack(track types.SubscribedTrack) { s.lock.Lock() oldTrack := s.subscribedTrack @@ -1573,6 +1601,7 @@ func (s *dataTrackSubscription) needsCleanup() bool { func (s *dataTrackSubscription) setDataDownTrack(dataDownTrack types.DataDownTrack) { s.lock.Lock() + oldDataDownTrack := s.dataDownTrack s.dataDownTrack = dataDownTrack subscriptionOptions := s.subscriptionOptions s.lock.Unlock() @@ -1581,8 +1610,9 @@ func (s *dataTrackSubscription) setDataDownTrack(dataDownTrack types.DataDownTra s.logger.Debugw("restoring data track subscription options", "subscriptionOptions", logger.Proto(subscriptionOptions)) dataDownTrack.UpdateSubscriptionOptions(subscriptionOptions) } - - // DT-TODO - DataTrack close callback on previous if not nil?, see setSubscribedTrack for example + if oldDataDownTrack != nil { + oldDataDownTrack.OnClose(nil) + } } func (s *dataTrackSubscription) getDataDownTrack() types.DataDownTrack { diff --git a/pkg/rtc/subscriptionmanager_test.go b/pkg/rtc/subscriptionmanager_test.go index bfbb5bf04..6c4618927 100644 --- a/pkg/rtc/subscriptionmanager_test.go +++ b/pkg/rtc/subscriptionmanager_test.go @@ -204,9 +204,9 @@ func TestUnsubscribe(t *testing.T) { publisherID: "pubID", publisherIdentity: "pub", logger: logger.GetLogger(), + hasPermission: true, }, - hasPermission: true, - bound: true, + bound: true, } // a bunch of unfortunate manual wiring res := resolver.Resolve(nil, s.trackID) @@ -445,6 +445,84 @@ func TestSubscriptionLimits(t *testing.T) { require.Len(t, sm.GetSubscribedTracks(), 1) } +func TestSubscribeDataTrack(t *testing.T) { + t.Run("no track permission", func(t *testing.T) { + sm := newTestSubscriptionManager() + defer sm.Close(false) + resolver := newTestDataTrackResolver(false, true, "pub", "pubID") + sm.params.DataTrackResolver = resolver.Resolve + + sm.SubscribeToDataTrack("track") + sm.lock.RLock() + s := sm.dataTrackSubscriptions["track"] + sm.lock.RUnlock() + require.Eventually(t, func() bool { + return !s.getHasPermission() + }, subSettleTimeout, subCheckInterval, "should not have permission to subscribe") + + time.Sleep(subscriptionTimeout) + + // isDesired remains unchanged, no subscriber added to the data track + require.True(t, s.isDesired()) + require.True(t, s.needsSubscribe()) + require.Equal(t, 0, resolver.dataTrack.AddSubscriberCallCount()) + + // subscriber should have been notified that permission was denied + p := sm.params.Participant.(*typesfakes.FakeLocalParticipant) + require.Equal(t, 1, p.SendSubscriptionPermissionUpdateCallCount()) + _, _, allowed := p.SendSubscriptionPermissionUpdateArgsForCall(0) + require.False(t, allowed) + + // give permissions now + resolver.SetHasPermission(true) + + require.Eventually(t, func() bool { + return !s.needsSubscribe() + }, subSettleTimeout, subCheckInterval, "should be subscribed") + require.NotNil(t, s.getDataDownTrack()) + require.Equal(t, 1, resolver.dataTrack.AddSubscriberCallCount()) + }) + + t.Run("permission revoked while subscribed", func(t *testing.T) { + sm := newTestSubscriptionManager() + defer sm.Close(false) + resolver := newTestDataTrackResolver(true, true, "pub", "pubID") + sm.params.DataTrackResolver = resolver.Resolve + + sm.SubscribeToDataTrack("track") + sm.lock.RLock() + s := sm.dataTrackSubscriptions["track"] + sm.lock.RUnlock() + require.Eventually(t, func() bool { + return !s.needsSubscribe() + }, subSettleTimeout, subCheckInterval, "should be subscribed") + + // revoke permission and close the down track like the publisher would + // (DataTrack.RevokeDisallowedSubscribers -> RemoveSubscriber -> DataDownTrack.Close) + resolver.SetHasPermission(false) + ddt := s.getDataDownTrack() + require.NotNil(t, ddt) + setTestDataDownTrackClosed(t, ddt) + + // subscription stays desired but is denied on resubscribe attempts + require.Nil(t, s.getDataDownTrack()) + require.True(t, s.isDesired()) + require.Eventually(t, func() bool { + return !s.getHasPermission() + }, subSettleTimeout, subCheckInterval, "should not have permission to resubscribe") + require.True(t, s.needsSubscribe()) + require.Equal(t, 1, resolver.dataTrack.AddSubscriberCallCount()) + + // give permission back, should resubscribe + resolver.SetHasPermission(true) + + require.Eventually(t, func() bool { + return !s.needsSubscribe() && s.getDataDownTrack() != nil + }, subSettleTimeout, subCheckInterval, "should be resubscribed") + require.Equal(t, 2, resolver.dataTrack.AddSubscriberCallCount()) + }) +} + type testSubscriptionParams struct { SubscriptionLimitAudio int32 SubscriptionLimitVideo int32 @@ -526,6 +604,57 @@ func (t *testResolver) Resolve(_subscriber types.LocalParticipant, trackID livek return res } +type testDataTrackResolver struct { + lock sync.Mutex + hasPermission bool + hasTrack bool + pubIdentity livekit.ParticipantIdentity + pubID livekit.ParticipantID + + dataTrack *typesfakes.FakeDataTrack +} + +func newTestDataTrackResolver(hasPermission bool, hasTrack bool, pubIdentity livekit.ParticipantIdentity, pubID livekit.ParticipantID) *testDataTrackResolver { + r := &testDataTrackResolver{ + hasPermission: hasPermission, + hasTrack: hasTrack, + pubIdentity: pubIdentity, + pubID: pubID, + dataTrack: &typesfakes.FakeDataTrack{}, + } + r.dataTrack.PublisherIDReturns(pubID) + r.dataTrack.PublisherIdentityReturns(pubIdentity) + r.dataTrack.AddSubscriberCalls(func(sub types.LocalParticipant) (types.DataDownTrack, error) { + ddt := &typesfakes.FakeDataDownTrack{} + ddt.PublishDataTrackReturns(r.dataTrack) + return ddt, nil + }) + return r +} + +func (t *testDataTrackResolver) SetHasPermission(hasPermission bool) { + t.lock.Lock() + defer t.lock.Unlock() + t.hasPermission = hasPermission +} + +func (t *testDataTrackResolver) Resolve(_subscriber types.LocalParticipant, trackID livekit.TrackID) types.DataResolverResult { + t.lock.Lock() + defer t.lock.Unlock() + res := types.DataResolverResult{ + TrackChangedNotifier: utils.NewChangeNotifier(), + TrackRemovedNotifier: utils.NewChangeNotifier(), + HasPermission: t.hasPermission, + PublisherID: t.pubID, + PublisherIdentity: t.pubIdentity, + } + if t.hasTrack { + t.dataTrack.IDReturns(trackID) + res.DataTrack = t.dataTrack + } + return res +} + func setTestSubscribedTrackBound(t *testing.T, st types.SubscribedTrack) { fst, ok := st.(*typesfakes.FakeSubscribedTrack) require.True(t, ok) @@ -541,3 +670,10 @@ func setTestSubscribedTrackClosed(t *testing.T, st types.SubscribedTrack, isExpe fst.OnCloseArgsForCall(0)(isExpectedToResume) } + +func setTestDataDownTrackClosed(t *testing.T, ddt types.DataDownTrack) { + fddt, ok := ddt.(*typesfakes.FakeDataDownTrack) + require.True(t, ok) + + fddt.OnCloseArgsForCall(0)() +} diff --git a/pkg/rtc/transport.go b/pkg/rtc/transport.go index 8f6a5e50a..0848ae137 100644 --- a/pkg/rtc/transport.go +++ b/pkg/rtc/transport.go @@ -232,6 +232,8 @@ type PCTransport struct { resetShortConnOnICERestart atomic.Bool signalingRTT atomic.Uint32 // milliseconds + hasFullyEstablishedRecorded bool + debouncedNegotiate *sfuutils.Debouncer debouncePending bool lastNegotiate time.Time @@ -717,6 +719,8 @@ func (t *PCTransport) setICEConnectedAt(at time.Time) { t.tcpICETimer.Stop() t.tcpICETimer = nil } + + prometheus.RecordPeerConnectionState(t.params.Transport, "ice_connected") } if t.mayFailedICEStatsTimer != nil { @@ -801,6 +805,7 @@ func (t *PCTransport) setConnectedAt(at time.Time) bool { t.firstConnectedAt = at prometheus.RecordServiceOperationSuccess("peer_connection") + prometheus.RecordPeerConnectionState(t.params.Transport, "connected") t.lock.Unlock() return true } @@ -964,6 +969,13 @@ func (t *PCTransport) onDataChannel(dc *webrtc.DataChannel) { func (t *PCTransport) maybeNotifyFullyEstablished() { if t.isFullyEstablished() { t.params.Handler.OnFullyEstablished() + + t.lock.Lock() + if !t.hasFullyEstablishedRecorded { + t.hasFullyEstablishedRecorded = true + prometheus.RecordPeerConnectionState(t.params.Transport, "fully_established") + } + t.lock.Unlock() } } @@ -2604,6 +2616,8 @@ func (t *PCTransport) createAndSendOffer(options *webrtc.OfferOptions) error { t.params.Logger.Debugw("local offer (unfiltered)", "sdp", offer.SDP) } + isStartOfConnectionSequence := t.pc.LocalDescription() == nil + err = t.pc.SetLocalDescription(offer) if err != nil { if errors.Is(err, webrtc.ErrConnectionClosed) { @@ -2615,6 +2629,10 @@ func (t *PCTransport) createAndSendOffer(options *webrtc.OfferOptions) error { return errors.Wrap(err, "setting local description failed") } + if isStartOfConnectionSequence { + prometheus.RecordPeerConnectionState(t.params.Transport, "started") + } + // // Filter after setting local description as pion expects the offer // to match between CreateOffer and SetLocalDescription. @@ -2788,7 +2806,7 @@ func (t *PCTransport) createAndSendAnswer() error { return errors.Wrap(err, "could not send answer") } t.localAnswerId.Store(answerId) - prometheus.RecordServiceOperationSuccess("asnwer") + prometheus.RecordServiceOperationSuccess("answer") if err := t.sendUnmatchedMediaRequirement(false); err != nil { return err @@ -2860,9 +2878,16 @@ func (t *PCTransport) handleRemoteOfferReceived(sd *webrtc.SessionDescription, o t.outputAndClearICEStats() } + isStartOfConnectionSequence := t.pc.RemoteDescription() == nil + if err := t.setRemoteDescription(*sd); err != nil { return err } + + if isStartOfConnectionSequence { + prometheus.RecordPeerConnectionState(t.params.Transport, "started") + } + t.params.Handler.OnSetRemoteDescriptionOffer() t.processSendersPendingConfig() diff --git a/pkg/rtc/types/interfaces.go b/pkg/rtc/types/interfaces.go index 7e67846fb..f2ac85829 100644 --- a/pkg/rtc/types/interfaces.go +++ b/pkg/rtc/types/interfaces.go @@ -444,6 +444,7 @@ type LocalParticipant interface { // permissions ClaimGrants() *auth.ClaimGrants + TokenExpiresAt() time.Time SetPermission(permission *livekit.ParticipantPermission) bool CanPublish() bool CanPublishSource(source livekit.TrackSource) bool @@ -839,6 +840,7 @@ type DataTrack interface { AddSubscriber(sub LocalParticipant) (DataDownTrack, error) RemoveSubscriber(participantID livekit.ParticipantID) IsSubscriber(subID livekit.ParticipantID) bool + RevokeDisallowedSubscribers(allowedSubscriberIdentities []livekit.ParticipantIdentity) []livekit.ParticipantIdentity AddDataDownTrack(sender DataTrackSender) error DeleteDataDownTrack(subscriberID livekit.ParticipantID) @@ -851,6 +853,7 @@ type DataTrack interface { //counterfeiter:generate . DataDownTrack type DataDownTrack interface { Close() + OnClose(fn func()) Handle() uint16 PublishDataTrack() DataTrack @@ -915,8 +918,10 @@ type DataResolverResult struct { TrackChangedNotifier ChangeNotifier TrackRemovedNotifier ChangeNotifier DataTrack DataTrack - PublisherID livekit.ParticipantID - PublisherIdentity livekit.ParticipantIdentity + // is permission given to the requesting participant + HasPermission bool + PublisherID livekit.ParticipantID + PublisherIdentity livekit.ParticipantIdentity } // MediaTrackResolver locates a specific media track for a subscriber diff --git a/pkg/rtc/types/typesfakes/fake_data_down_track.go b/pkg/rtc/types/typesfakes/fake_data_down_track.go index 56d0d102d..027fee113 100644 --- a/pkg/rtc/types/typesfakes/fake_data_down_track.go +++ b/pkg/rtc/types/typesfakes/fake_data_down_track.go @@ -23,6 +23,11 @@ type FakeDataDownTrack struct { handleReturnsOnCall map[int]struct { result1 uint16 } + OnCloseStub func(func()) + onCloseMutex sync.RWMutex + onCloseArgsForCall []struct { + arg1 func() + } PublishDataTrackStub func() types.DataTrack publishDataTrackMutex sync.RWMutex publishDataTrackArgsForCall []struct { @@ -119,6 +124,38 @@ func (fake *FakeDataDownTrack) HandleReturnsOnCall(i int, result1 uint16) { }{result1} } +func (fake *FakeDataDownTrack) OnClose(arg1 func()) { + fake.onCloseMutex.Lock() + fake.onCloseArgsForCall = append(fake.onCloseArgsForCall, struct { + arg1 func() + }{arg1}) + stub := fake.OnCloseStub + fake.recordInvocation("OnClose", []interface{}{arg1}) + fake.onCloseMutex.Unlock() + if stub != nil { + fake.OnCloseStub(arg1) + } +} + +func (fake *FakeDataDownTrack) OnCloseCallCount() int { + fake.onCloseMutex.RLock() + defer fake.onCloseMutex.RUnlock() + return len(fake.onCloseArgsForCall) +} + +func (fake *FakeDataDownTrack) OnCloseCalls(stub func(func())) { + fake.onCloseMutex.Lock() + defer fake.onCloseMutex.Unlock() + fake.OnCloseStub = stub +} + +func (fake *FakeDataDownTrack) OnCloseArgsForCall(i int) func() { + fake.onCloseMutex.RLock() + defer fake.onCloseMutex.RUnlock() + argsForCall := fake.onCloseArgsForCall[i] + return argsForCall.arg1 +} + func (fake *FakeDataDownTrack) PublishDataTrack() types.DataTrack { fake.publishDataTrackMutex.Lock() ret, specificReturn := fake.publishDataTrackReturnsOnCall[len(fake.publishDataTrackArgsForCall)] diff --git a/pkg/rtc/types/typesfakes/fake_data_track.go b/pkg/rtc/types/typesfakes/fake_data_track.go index c54d77c54..07a49f9b1 100644 --- a/pkg/rtc/types/typesfakes/fake_data_track.go +++ b/pkg/rtc/types/typesfakes/fake_data_track.go @@ -116,6 +116,17 @@ type FakeDataTrack struct { removeSubscriberArgsForCall []struct { arg1 livekit.ParticipantID } + RevokeDisallowedSubscribersStub func([]livekit.ParticipantIdentity) []livekit.ParticipantIdentity + revokeDisallowedSubscribersMutex sync.RWMutex + revokeDisallowedSubscribersArgsForCall []struct { + arg1 []livekit.ParticipantIdentity + } + revokeDisallowedSubscribersReturns struct { + result1 []livekit.ParticipantIdentity + } + revokeDisallowedSubscribersReturnsOnCall map[int]struct { + result1 []livekit.ParticipantIdentity + } ToProtoStub func() *livekit.DataTrackInfo toProtoMutex sync.RWMutex toProtoArgsForCall []struct { @@ -708,6 +719,72 @@ func (fake *FakeDataTrack) RemoveSubscriberArgsForCall(i int) livekit.Participan return argsForCall.arg1 } +func (fake *FakeDataTrack) RevokeDisallowedSubscribers(arg1 []livekit.ParticipantIdentity) []livekit.ParticipantIdentity { + var arg1Copy []livekit.ParticipantIdentity + if arg1 != nil { + arg1Copy = make([]livekit.ParticipantIdentity, len(arg1)) + copy(arg1Copy, arg1) + } + fake.revokeDisallowedSubscribersMutex.Lock() + ret, specificReturn := fake.revokeDisallowedSubscribersReturnsOnCall[len(fake.revokeDisallowedSubscribersArgsForCall)] + fake.revokeDisallowedSubscribersArgsForCall = append(fake.revokeDisallowedSubscribersArgsForCall, struct { + arg1 []livekit.ParticipantIdentity + }{arg1Copy}) + stub := fake.RevokeDisallowedSubscribersStub + fakeReturns := fake.revokeDisallowedSubscribersReturns + fake.recordInvocation("RevokeDisallowedSubscribers", []interface{}{arg1Copy}) + fake.revokeDisallowedSubscribersMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDataTrack) RevokeDisallowedSubscribersCallCount() int { + fake.revokeDisallowedSubscribersMutex.RLock() + defer fake.revokeDisallowedSubscribersMutex.RUnlock() + return len(fake.revokeDisallowedSubscribersArgsForCall) +} + +func (fake *FakeDataTrack) RevokeDisallowedSubscribersCalls(stub func([]livekit.ParticipantIdentity) []livekit.ParticipantIdentity) { + fake.revokeDisallowedSubscribersMutex.Lock() + defer fake.revokeDisallowedSubscribersMutex.Unlock() + fake.RevokeDisallowedSubscribersStub = stub +} + +func (fake *FakeDataTrack) RevokeDisallowedSubscribersArgsForCall(i int) []livekit.ParticipantIdentity { + fake.revokeDisallowedSubscribersMutex.RLock() + defer fake.revokeDisallowedSubscribersMutex.RUnlock() + argsForCall := fake.revokeDisallowedSubscribersArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDataTrack) RevokeDisallowedSubscribersReturns(result1 []livekit.ParticipantIdentity) { + fake.revokeDisallowedSubscribersMutex.Lock() + defer fake.revokeDisallowedSubscribersMutex.Unlock() + fake.RevokeDisallowedSubscribersStub = nil + fake.revokeDisallowedSubscribersReturns = struct { + result1 []livekit.ParticipantIdentity + }{result1} +} + +func (fake *FakeDataTrack) RevokeDisallowedSubscribersReturnsOnCall(i int, result1 []livekit.ParticipantIdentity) { + fake.revokeDisallowedSubscribersMutex.Lock() + defer fake.revokeDisallowedSubscribersMutex.Unlock() + fake.RevokeDisallowedSubscribersStub = nil + if fake.revokeDisallowedSubscribersReturnsOnCall == nil { + fake.revokeDisallowedSubscribersReturnsOnCall = make(map[int]struct { + result1 []livekit.ParticipantIdentity + }) + } + fake.revokeDisallowedSubscribersReturnsOnCall[i] = struct { + result1 []livekit.ParticipantIdentity + }{result1} +} + func (fake *FakeDataTrack) ToProto() *livekit.DataTrackInfo { fake.toProtoMutex.Lock() ret, specificReturn := fake.toProtoReturnsOnCall[len(fake.toProtoArgsForCall)] diff --git a/pkg/rtc/types/typesfakes/fake_local_participant.go b/pkg/rtc/types/typesfakes/fake_local_participant.go index dadab8550..fec988305 100644 --- a/pkg/rtc/types/typesfakes/fake_local_participant.go +++ b/pkg/rtc/types/typesfakes/fake_local_participant.go @@ -1384,6 +1384,16 @@ type FakeLocalParticipant struct { result1 *livekit.ParticipantInfo result2 utils.TimedVersion } + TokenExpiresAtStub func() time.Time + tokenExpiresAtMutex sync.RWMutex + tokenExpiresAtArgsForCall []struct { + } + tokenExpiresAtReturns struct { + result1 time.Time + } + tokenExpiresAtReturnsOnCall map[int]struct { + result1 time.Time + } UncacheDownTrackStub func(*webrtc.RTPTransceiver) uncacheDownTrackMutex sync.RWMutex uncacheDownTrackArgsForCall []struct { @@ -8901,6 +8911,59 @@ func (fake *FakeLocalParticipant) ToProtoWithVersionReturnsOnCall(i int, result1 }{result1, result2} } +func (fake *FakeLocalParticipant) TokenExpiresAt() time.Time { + fake.tokenExpiresAtMutex.Lock() + ret, specificReturn := fake.tokenExpiresAtReturnsOnCall[len(fake.tokenExpiresAtArgsForCall)] + fake.tokenExpiresAtArgsForCall = append(fake.tokenExpiresAtArgsForCall, struct { + }{}) + stub := fake.TokenExpiresAtStub + fakeReturns := fake.tokenExpiresAtReturns + fake.recordInvocation("TokenExpiresAt", []interface{}{}) + fake.tokenExpiresAtMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeLocalParticipant) TokenExpiresAtCallCount() int { + fake.tokenExpiresAtMutex.RLock() + defer fake.tokenExpiresAtMutex.RUnlock() + return len(fake.tokenExpiresAtArgsForCall) +} + +func (fake *FakeLocalParticipant) TokenExpiresAtCalls(stub func() time.Time) { + fake.tokenExpiresAtMutex.Lock() + defer fake.tokenExpiresAtMutex.Unlock() + fake.TokenExpiresAtStub = stub +} + +func (fake *FakeLocalParticipant) TokenExpiresAtReturns(result1 time.Time) { + fake.tokenExpiresAtMutex.Lock() + defer fake.tokenExpiresAtMutex.Unlock() + fake.TokenExpiresAtStub = nil + fake.tokenExpiresAtReturns = struct { + result1 time.Time + }{result1} +} + +func (fake *FakeLocalParticipant) TokenExpiresAtReturnsOnCall(i int, result1 time.Time) { + fake.tokenExpiresAtMutex.Lock() + defer fake.tokenExpiresAtMutex.Unlock() + fake.TokenExpiresAtStub = nil + if fake.tokenExpiresAtReturnsOnCall == nil { + fake.tokenExpiresAtReturnsOnCall = make(map[int]struct { + result1 time.Time + }) + } + fake.tokenExpiresAtReturnsOnCall[i] = struct { + result1 time.Time + }{result1} +} + func (fake *FakeLocalParticipant) UncacheDownTrack(arg1 *webrtc.RTPTransceiver) { fake.uncacheDownTrackMutex.Lock() fake.uncacheDownTrackArgsForCall = append(fake.uncacheDownTrackArgsForCall, struct { diff --git a/pkg/rtc/uptrackmanager.go b/pkg/rtc/uptrackmanager.go index f75f501c8..fefc7f459 100644 --- a/pkg/rtc/uptrackmanager.go +++ b/pkg/rtc/uptrackmanager.go @@ -49,7 +49,7 @@ type UpTrackManager struct { // publishedTracks that participant is publishing publishedTracks map[livekit.TrackID]types.MediaTrack subscriptionPermission *livekit.SubscriptionPermission - // subscriber permission for published tracks + // subscriber permission for published tracks (both media and data) subscriberPermissions map[livekit.ParticipantIdentity]*livekit.TrackPermission // subscriberIdentity => *livekit.TrackPermission lock sync.RWMutex @@ -235,6 +235,13 @@ func (u *UpTrackManager) HasPermission(trackID livekit.TrackID, subIdentity live return u.hasPermissionLocked(trackID, subIdentity) } +func (u *UpTrackManager) GetAllowedSubscribers(trackID livekit.TrackID) []livekit.ParticipantIdentity { + u.lock.RLock() + defer u.lock.RUnlock() + + return u.getAllowedSubscribersLocked(trackID) +} + func (u *UpTrackManager) UpdatePublishedAudioTrack(update *livekit.UpdateLocalAudioTrack) types.MediaTrack { track := u.GetPublishedTrack(livekit.TrackID(update.TrackSid)) if track != nil { diff --git a/pkg/service/auth.go b/pkg/service/auth.go index f8e6442b5..d019c4aae 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -19,6 +19,7 @@ import ( "errors" "net/http" "strings" + "time" "github.com/twitchtv/twirp" @@ -35,8 +36,9 @@ const ( type grantsKey struct{} type grantsValue struct { - claims *auth.ClaimGrants - apiKey string + claims *auth.ClaimGrants + apiKey string + expiresAt time.Time } var ( @@ -90,17 +92,23 @@ func (m *APIKeyAuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, return } - _, grants, err := v.Verify(secret) + claims, grants, err := v.Verify(secret) if err != nil { HandleError(w, r, http.StatusUnauthorized, errors.New("invalid token: "+authToken+", error: "+err.Error())) return } + var expiresAt time.Time + if claims != nil && claims.ExpiresAt != nil { + expiresAt = claims.ExpiresAt.Time + } + // set grants in context ctx := r.Context() r = r.WithContext(context.WithValue(ctx, grantsKey{}, &grantsValue{ - claims: grants, - apiKey: v.APIKey(), + claims: grants, + apiKey: v.APIKey(), + expiresAt: expiresAt, })) } @@ -123,6 +131,15 @@ func GetGrants(ctx context.Context) *auth.ClaimGrants { return v.claims } +func GetTokenExpiresAt(ctx context.Context) time.Time { + val := ctx.Value(grantsKey{}) + v, ok := val.(*grantsValue) + if !ok { + return time.Time{} + } + return v.expiresAt +} + func GetAPIKey(ctx context.Context) string { val := ctx.Value(grantsKey{}) v, ok := val.(*grantsValue) @@ -133,9 +150,14 @@ func GetAPIKey(ctx context.Context) string { } func WithGrants(ctx context.Context, grants *auth.ClaimGrants, apiKey string) context.Context { + return WithGrantsExpiry(ctx, grants, apiKey, time.Time{}) +} + +func WithGrantsExpiry(ctx context.Context, grants *auth.ClaimGrants, apiKey string, expiresAt time.Time) context.Context { return context.WithValue(ctx, grantsKey{}, &grantsValue{ - claims: grants, - apiKey: apiKey, + claims: grants, + apiKey: apiKey, + expiresAt: expiresAt, }) } diff --git a/pkg/service/docker_test.go b/pkg/service/docker_test.go index a3da60484..9fc5b90bb 100644 --- a/pkg/service/docker_test.go +++ b/pkg/service/docker_test.go @@ -15,27 +15,31 @@ package service_test import ( + "context" "fmt" "log" "net" "os" "testing" + "time" "go.uber.org/atomic" - "github.com/ory/dockertest/v3" + mobyclient "github.com/moby/moby/client" + "github.com/ory/dockertest/v4" ) -var Docker *dockertest.Pool +var Docker dockertest.ClosablePool func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") + ctx := context.Background() + pool, err := dockertest.NewPool(ctx, "") if err != nil { log.Fatalf("Could not construct pool: %s", err) } // uses pool to try to connect to Docker - err = pool.Client.Ping() + _, err = pool.Client().Ping(ctx, mobyclient.PingOptions{}) if err != nil { log.Fatalf("Could not connect to Docker: %s", err) } @@ -46,7 +50,7 @@ func TestMain(m *testing.M) { } func waitTCPPort(t testing.TB, addr string) { - if err := Docker.Retry(func() error { + if err := Docker.Retry(t.Context(), 30*time.Second, func() error { conn, err := net.Dial("tcp", addr) if err != nil { t.Log(err) @@ -62,15 +66,18 @@ func waitTCPPort(t testing.TB, addr string) { var redisLast atomic.Uint32 func runRedis(t testing.TB) string { - c, err := Docker.RunWithOptions(&dockertest.RunOptions{ - Name: fmt.Sprintf("lktest-redis-%d", redisLast.Inc()), - Repository: "redis", Tag: "latest", - }) + c, err := Docker.Run(t.Context(), + "redis", + dockertest.WithName(fmt.Sprintf("lktest-redis-%d", redisLast.Inc())), + dockertest.WithTag("latest"), + ) if err != nil { t.Fatal(err) } t.Cleanup(func() { - _ = Docker.Purge(c) + // t.Context() is canceled before cleanup funcs run, so use a + // non-canceled context to let the container stop/remove complete. + _ = c.Close(context.Background()) }) addr := c.GetHostPort("6379/tcp") waitTCPPort(t, addr) diff --git a/pkg/service/egress.go b/pkg/service/egress.go index 961685de0..697aecbb4 100644 --- a/pkg/service/egress.go +++ b/pkg/service/egress.go @@ -72,6 +72,33 @@ func NewEgressLauncher(client rpc.EgressClient, io IOClient, store ServiceStore) } } +func (s *EgressService) StartEgress(ctx context.Context, req *livekit.StartEgressRequest) (*livekit.EgressInfo, error) { + sourceType, outputType := egress.GetTypes(&livekit.EgressInfo_Egress{Egress: req}) + fields := []any{ + "room", req.RoomName, + "sourceType", sourceType, + "outputType", outputType, + } + defer func() { + AppendLogFields(ctx, fields...) + }() + + egressID, idFromCtx := EgressID(ctx) + ei, err := s.startEgress(ctx, &rpc.StartEgressRequest{ + EgressId: egressID, + Request: &rpc.StartEgressRequest_Egress{ + Egress: req, + }, + }) + if err != nil { + return nil, err + } + if !idFromCtx { + fields = append(fields, "egressID", ei.EgressId) + } + return ei, err +} + func (s *EgressService) StartRoomCompositeEgress(ctx context.Context, req *livekit.RoomCompositeEgressRequest) (*livekit.EgressInfo, error) { fields := []any{ "room", req.RoomName, @@ -230,6 +257,8 @@ func (s *egressLauncher) StartEgress(ctx context.Context, req *rpc.StartEgressRe roomName = v.TrackComposite.RoomName case *rpc.StartEgressRequest_Track: roomName = v.Track.RoomName + case *rpc.StartEgressRequest_Egress: + roomName = v.Egress.RoomName } if roomName != "" { @@ -254,13 +283,6 @@ func (s *egressLauncher) StartEgress(ctx context.Context, req *rpc.StartEgressRe return info, nil } -func (s *egressLauncher) StopEgress(ctx context.Context, req *livekit.StopEgressRequest) (*livekit.EgressInfo, error) { - if s.client == nil { - return nil, ErrEgressNotConnected - } - return s.client.StopEgress(ctx, req.EgressId, req) -} - type LayoutMetadata struct { Layout string `json:"layout"` } @@ -371,3 +393,10 @@ func (s *EgressService) StopEgress(ctx context.Context, req *livekit.StopEgressR return info, nil } + +func (s *egressLauncher) StopEgress(ctx context.Context, req *livekit.StopEgressRequest) (*livekit.EgressInfo, error) { + if s.client == nil { + return nil, ErrEgressNotConnected + } + return s.client.StopEgress(ctx, req.EgressId, req) +} diff --git a/pkg/service/roommanager.go b/pkg/service/roommanager.go index b70844bd0..5a2208551 100644 --- a/pkg/service/roommanager.go +++ b/pkg/service/roommanager.go @@ -471,34 +471,37 @@ func (r *RoomManager) StartSession( } participant, err = rtc.NewParticipant(rtc.ParticipantParams{ - Identity: pi.Identity, - Name: pi.Name, - SID: sid, - Config: &rtcConf, - Sink: responseSink, - AudioConfig: r.config.Audio, - VideoConfig: r.config.Video, - LimitConfig: r.config.Limit, - ProtocolVersion: pv, - SessionStartTime: sessionStartTime, - SessionTimer: observability.NewSessionTimer(sessionStartTime), - TelemetryListener: room.ParticipantTelemetryListener(), - Trailer: room.Trailer(), - PLIThrottleConfig: r.config.RTC.PLIThrottle, - CongestionControlConfig: r.config.RTC.CongestionControl, - PublishEnabledCodecs: enabledCodecs, - SubscribeEnabledCodecs: enabledCodecs, - Grants: pi.Grants, - Reconnect: pi.Reconnect, - Logger: pLogger, - Reporter: roomobs.NewNoopParticipantSessionReporter(), - ClientConf: clientConf, - ClientInfo: rtc.ClientInfo{ClientInfo: pi.Client}, - Region: pi.Region, - AdaptiveStream: pi.AdaptiveStream, - AllowTCPFallback: allowFallback, - TURNSEnabled: r.config.IsTURNSEnabled(), - ParticipantListener: room.LocalParticipantListener(), + Identity: pi.Identity, + Name: pi.Name, + SID: sid, + Config: &rtcConf, + Sink: responseSink, + AudioConfig: r.config.Audio, + VideoConfig: r.config.Video, + LimitConfig: r.config.Limit, + ProtocolVersion: pv, + SessionStartTime: sessionStartTime, + SessionTimer: observability.NewSessionTimer(sessionStartTime), + TelemetryListener: room.ParticipantTelemetryListener(), + Trailer: room.Trailer(), + PLIThrottleConfig: r.config.RTC.PLIThrottle, + CongestionControlConfig: r.config.RTC.CongestionControl, + PublishEnabledCodecs: enabledCodecs, + SubscribeEnabledCodecs: enabledCodecs, + Grants: pi.Grants, + TokenExpiresAt: pi.TokenExpiresAt, + Reconnect: pi.Reconnect, + Logger: pLogger, + Reporter: roomobs.NewNoopParticipantSessionReporter(), + ClientConf: clientConf, + ClientInfo: rtc.ClientInfo{ClientInfo: pi.Client}, + Region: pi.Region, + AdaptiveStream: pi.AdaptiveStream, + AllowTCPFallback: allowFallback, + TCPFallbackRTTThreshold: r.config.RTC.TCPFallbackRTTThreshold, + AllowUDPUnstableFallback: r.config.RTC.AllowUDPUnstableFallback, + TURNSEnabled: r.config.IsTURNSEnabled(), + ParticipantListener: room.LocalParticipantListener(), ParticipantHelper: &roomManagerParticipantHelper{ room: room, codecRegressionThreshold: r.config.Video.CodecRegressionThreshold, @@ -1123,11 +1126,20 @@ func (r *RoomManager) refreshToken(participant types.LocalParticipant) error { } grants := participant.ClaimGrants() + + // Preserve the original token's expiry + validFor := tokenDefaultTTL + if expiresAt := participant.TokenExpiresAt(); !expiresAt.IsZero() { + if remaining := time.Until(expiresAt); remaining > validFor { + validFor = remaining + } + } + token := auth.NewAccessToken(key, secret) token.SetName(grants.Name). SetIdentity(string(participant.Identity())). SetKind(grants.GetParticipantKind()). - SetValidFor(tokenDefaultTTL). + SetValidFor(validFor). SetMetadata(grants.Metadata). SetAttributes(grants.Attributes). SetVideoGrant(grants.Video). diff --git a/pkg/service/rtcservice.go b/pkg/service/rtcservice.go index 6f18356df..d4f83e7cb 100644 --- a/pkg/service/rtcservice.go +++ b/pkg/service/rtcservice.go @@ -218,6 +218,7 @@ func (s *RTCService) validateInternal( Identity: livekit.ParticipantIdentity(res.grants.Identity), Name: livekit.ParticipantName(res.grants.Name), Grants: res.grants, + TokenExpiresAt: res.tokenExpiresAt, Region: res.region, CreateRoom: res.createRoomRequest, UseSinglePeerConnection: useSinglePeerConnection, @@ -364,6 +365,7 @@ func (s *RTCService) serve(w http.ResponseWriter, r *http.Request, needsJoinRequ roomName, pi, code, err = s.validateInternal(pLogger, r, needsJoinRequest, false) if err != nil { + prometheus.IncrementParticipantJoinValidationFail(1) resolveLogger(true) HandleError(w, r, code, err, getLoggerFields()...) return diff --git a/pkg/service/server.go b/pkg/service/server.go index 4ca8561b0..05a51522c 100644 --- a/pkg/service/server.go +++ b/pkg/service/server.go @@ -21,7 +21,7 @@ import ( "fmt" "net" "net/http" - _ "net/http/pprof" + httppprof "net/http/pprof" "runtime" "runtime/pprof" "strconv" @@ -53,6 +53,7 @@ type LivekitServer struct { agentService *AgentService httpServer *http.Server promServer *http.Server + debugServer *http.Server router routing.Router roomManager *RoomManager signalServer *SignalServer @@ -171,6 +172,20 @@ func NewLivekitServer(conf *config.Config, } } + if conf.DebugHandler.Port > 0 { + debugMux := http.NewServeMux() + debugMux.HandleFunc("/debug/pprof/", httppprof.Index) + debugMux.HandleFunc("/debug/pprof/cmdline", httppprof.Cmdline) + debugMux.HandleFunc("/debug/pprof/profile", httppprof.Profile) + debugMux.HandleFunc("/debug/pprof/symbol", httppprof.Symbol) + debugMux.HandleFunc("/debug/pprof/trace", httppprof.Trace) + debugMux.HandleFunc("/debug/goroutine", s.debugGoroutines) + debugMux.HandleFunc("/debug/rooms", s.debugInfo) + s.debugServer = &http.Server{ + Handler: http.Handler(debugMux), + } + } + if err = router.RemoveDeadNodes(); err != nil { return } @@ -221,6 +236,7 @@ func (s *LivekitServer) Start() error { // ensure we could listen listeners := make([]net.Listener, 0) promListeners := make([]net.Listener, 0) + debugListeners := make([]net.Listener, 0) for _, addr := range addresses { ln, err := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(int(s.config.Port)))) if err != nil { @@ -235,6 +251,14 @@ func (s *LivekitServer) Start() error { } promListeners = append(promListeners, ln) } + + if s.debugServer != nil { + ln, err = net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(int(s.config.DebugHandler.Port)))) + if err != nil { + return err + } + debugListeners = append(debugListeners, ln) + } } values := []any{ @@ -259,6 +283,9 @@ func (s *LivekitServer) Start() error { if s.config.Prometheus.Port != 0 { values = append(values, "portPrometheus", s.config.Prometheus.Port) } + if s.config.DebugHandler.Port != 0 { + values = append(values, "portDebugHandler", s.config.DebugHandler.Port) + } if s.config.Region != "" { values = append(values, "region", s.config.Region) } @@ -271,6 +298,10 @@ func (s *LivekitServer) Start() error { go s.promServer.Serve(promLn) } + for _, debugLn := range debugListeners { + go s.debugServer.Serve(debugLn) + } + if err := s.signalServer.Start(); err != nil { return err } @@ -302,6 +333,9 @@ func (s *LivekitServer) Start() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() _ = s.httpServer.Shutdown(ctx) + if s.debugServer != nil { + _ = s.debugServer.Shutdown(ctx) + } if s.turnServer != nil { _ = s.turnServer.Close() diff --git a/pkg/service/utils.go b/pkg/service/utils.go index e129e2f83..be7c0dc99 100644 --- a/pkg/service/utils.go +++ b/pkg/service/utils.go @@ -28,6 +28,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/ua-parser/uap-go/uaparser" "gopkg.in/yaml.v3" @@ -289,6 +290,7 @@ type ValidateConnectRequestParams struct { type ValidateConnectRequestResult struct { roomName livekit.RoomName grants *auth.ClaimGrants + tokenExpiresAt time.Time region string createRoomRequest *livekit.CreateRoomRequest } @@ -402,6 +404,7 @@ func ValidateConnectRequest( } res.grants = claims + res.tokenExpiresAt = GetTokenExpiresAt(r.Context()) return res, http.StatusOK, nil } diff --git a/pkg/service/wire_gen.go b/pkg/service/wire_gen.go index 5e9eff96c..45ebcf4d2 100644 --- a/pkg/service/wire_gen.go +++ b/pkg/service/wire_gen.go @@ -29,10 +29,6 @@ import ( "os" ) -import ( - _ "net/http/pprof" -) - // Injectors from wire.go: func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*LivekitServer, error) { diff --git a/pkg/telemetry/events.go b/pkg/telemetry/events.go index fb32e4c37..55a21f6a1 100644 --- a/pkg/telemetry/events.go +++ b/pkg/telemetry/events.go @@ -135,6 +135,7 @@ func (t *telemetryService) ParticipantActive( prometheus.AddParticipant() } worker.SetConnected() + prometheus.IncrementParticipantRtcActive(1) ev := newParticipantEvent(livekit.AnalyticsEventType_PARTICIPANT_ACTIVE, room, participant) ev.ClientMeta = clientMeta diff --git a/pkg/telemetry/prometheus/node.go b/pkg/telemetry/prometheus/node.go index 02229186f..c3a3fc3ca 100644 --- a/pkg/telemetry/prometheus/node.go +++ b/pkg/telemetry/prometheus/node.go @@ -149,40 +149,43 @@ func GetNodeStats(nodeStartedAt int64, prevStats []*livekit.NodeStats, rateInter promSysPacketGauge.WithLabelValues("dropped").Set(float64(sysDroppedPackets - sysDroppedPacketsStart)) stats := &livekit.NodeStats{ - StartedAt: nodeStartedAt, - UpdatedAt: time.Now().Unix(), - NumRooms: roomCurrent.Load(), - NumClients: participantCurrent.Load(), - NumTracksIn: trackPublishedCurrent.Load(), - NumTracksOut: trackSubscribedCurrent.Load(), - NumTrackPublishAttempts: trackPublishAttempts.Load(), - NumTrackPublishSuccess: trackPublishSuccess.Load(), - NumTrackPublishCancels: trackPublishCancels.Load(), - NumTrackSubscribeAttempts: trackSubscribeAttempts.Load(), - NumTrackSubscribeSuccess: trackSubscribeSuccess.Load(), - NumTrackSubscribeCancels: trackSubscribeCancels.Load(), - BytesIn: bytesIn.Load(), - BytesOut: bytesOut.Load(), - PacketsIn: packetsIn.Load(), - PacketsOut: packetsOut.Load(), - RetransmitBytesOut: retransmitBytes.Load(), - RetransmitPacketsOut: retransmitPackets.Load(), - NackTotal: nackTotal.Load(), - ParticipantSignalConnected: participantSignalConnected.Load(), - ParticipantRtcInit: participantRTCInit.Load(), - ParticipantRtcConnected: participantRTCConnected.Load(), - ParticipantRtcCanceled: participantRTCCanceled.Load(), - ForwardLatency: forwardLatency.Load(), - ForwardJitter: forwardJitter.Load(), - NumCpus: uint32(cpuStats.NumCPU()), // this will round down to the nearest integer - CpuLoad: float32(cpuStats.GetCPULoad()), - MemoryTotal: memTotal, - MemoryUsed: memUsed, - LoadAvgLast1Min: float32(loadAvg.Loadavg1), - LoadAvgLast5Min: float32(loadAvg.Loadavg5), - LoadAvgLast15Min: float32(loadAvg.Loadavg15), - SysPacketsOut: sysPackets, - SysPacketsDropped: sysDroppedPackets, + StartedAt: nodeStartedAt, + UpdatedAt: time.Now().Unix(), + NumRooms: roomCurrent.Load(), + NumClients: participantCurrent.Load(), + NumTracksIn: trackPublishedCurrent.Load(), + NumTracksOut: trackSubscribedCurrent.Load(), + NumTrackPublishAttempts: trackPublishAttempts.Load(), + NumTrackPublishSuccess: trackPublishSuccess.Load(), + NumTrackPublishCancels: trackPublishCancels.Load(), + NumTrackSubscribeAttempts: trackSubscribeAttempts.Load(), + NumTrackSubscribeSuccess: trackSubscribeSuccess.Load(), + NumTrackSubscribeCancels: trackSubscribeCancels.Load(), + BytesIn: bytesIn.Load(), + BytesOut: bytesOut.Load(), + PacketsIn: packetsIn.Load(), + PacketsOut: packetsOut.Load(), + RetransmitBytesOut: retransmitBytes.Load(), + RetransmitPacketsOut: retransmitPackets.Load(), + NackTotal: nackTotal.Load(), + ParticipantSignalConnected: participantSignalConnected.Load(), + ParticipantSignalFailed: participantSignalFailed.Load(), + ParticipantSignalValidationFailed: participantSignalValidationFailed.Load(), + ParticipantRtcInit: participantRTCInit.Load(), + ParticipantRtcConnected: participantRTCConnected.Load(), + ParticipantRtcCanceled: participantRTCCanceled.Load(), + ParticipantRtcActive: participantRTCActive.Load(), + ForwardLatency: forwardLatency.Load(), + ForwardJitter: forwardJitter.Load(), + NumCpus: uint32(cpuStats.NumCPU()), // this will round down to the nearest integer + CpuLoad: float32(cpuStats.GetCPULoad()), + MemoryTotal: memTotal, + MemoryUsed: memUsed, + LoadAvgLast1Min: float32(loadAvg.Loadavg1), + LoadAvgLast5Min: float32(loadAvg.Loadavg5), + LoadAvgLast15Min: float32(loadAvg.Loadavg15), + SysPacketsOut: sysPackets, + SysPacketsDropped: sysDroppedPackets, } for _, rateInterval := range rateIntervals { @@ -239,32 +242,35 @@ func getNodeStatsRate(statsHistory []*livekit.NodeStats) *livekit.NodeStatsRate earlier := statsHistory[0] later := statsHistory[len(statsHistory)-1] rate := &livekit.NodeStatsRate{ - StartedAt: earlier.UpdatedAt, - EndedAt: later.UpdatedAt, - Duration: elapsed, - BytesIn: perSec(earlier.BytesIn, later.BytesIn, elapsed), - BytesOut: perSec(earlier.BytesOut, later.BytesOut, elapsed), - PacketsIn: perSec(earlier.PacketsIn, later.PacketsIn, elapsed), - PacketsOut: perSec(earlier.PacketsOut, later.PacketsOut, elapsed), - RetransmitBytesOut: perSec(earlier.RetransmitBytesOut, later.RetransmitBytesOut, elapsed), - RetransmitPacketsOut: perSec(earlier.RetransmitPacketsOut, later.RetransmitPacketsOut, elapsed), - NackTotal: perSec(earlier.NackTotal, later.NackTotal, elapsed), - ParticipantSignalConnected: perSec(earlier.ParticipantSignalConnected, later.ParticipantSignalConnected, elapsed), - ParticipantRtcInit: perSec(earlier.ParticipantRtcInit, later.ParticipantRtcInit, elapsed), - ParticipantRtcConnected: perSec(earlier.ParticipantRtcConnected, later.ParticipantRtcConnected, elapsed), - ParticipantRtcCanceled: perSec(earlier.ParticipantRtcCanceled, later.ParticipantRtcCanceled, elapsed), - SysPacketsOut: perSec(uint64(earlier.SysPacketsOut), uint64(later.SysPacketsOut), elapsed), - SysPacketsDropped: perSec(uint64(earlier.SysPacketsDropped), uint64(later.SysPacketsDropped), elapsed), - TrackPublishAttempts: perSec(uint64(earlier.NumTrackPublishAttempts), uint64(later.NumTrackPublishAttempts), elapsed), - TrackPublishSuccess: perSec(uint64(earlier.NumTrackPublishSuccess), uint64(later.NumTrackPublishSuccess), elapsed), - TrackPublishCancels: perSec(uint64(earlier.NumTrackPublishCancels), uint64(later.NumTrackPublishCancels), elapsed), - TrackSubscribeAttempts: perSec(uint64(earlier.NumTrackSubscribeAttempts), uint64(later.NumTrackSubscribeAttempts), elapsed), - TrackSubscribeSuccess: perSec(uint64(earlier.NumTrackSubscribeSuccess), uint64(later.NumTrackSubscribeSuccess), elapsed), - TrackSubscribeCancels: perSec(uint64(earlier.NumTrackSubscribeCancels), uint64(later.NumTrackSubscribeCancels), elapsed), - CpuLoad: cpuLoad / float32(elapsed), - MemoryLoad: memoryLoad / float32(elapsed), - MemoryUsed: memoryUsed / float32(elapsed), - MemoryTotal: memoryTotal / float32(elapsed), + StartedAt: earlier.UpdatedAt, + EndedAt: later.UpdatedAt, + Duration: elapsed, + BytesIn: perSec(earlier.BytesIn, later.BytesIn, elapsed), + BytesOut: perSec(earlier.BytesOut, later.BytesOut, elapsed), + PacketsIn: perSec(earlier.PacketsIn, later.PacketsIn, elapsed), + PacketsOut: perSec(earlier.PacketsOut, later.PacketsOut, elapsed), + RetransmitBytesOut: perSec(earlier.RetransmitBytesOut, later.RetransmitBytesOut, elapsed), + RetransmitPacketsOut: perSec(earlier.RetransmitPacketsOut, later.RetransmitPacketsOut, elapsed), + NackTotal: perSec(earlier.NackTotal, later.NackTotal, elapsed), + ParticipantSignalConnected: perSec(earlier.ParticipantSignalConnected, later.ParticipantSignalConnected, elapsed), + ParticipantSignalFailed: perSec(earlier.ParticipantSignalFailed, later.ParticipantSignalFailed, elapsed), + ParticipantSignalValidationFailed: perSec(earlier.ParticipantSignalValidationFailed, later.ParticipantSignalValidationFailed, elapsed), + ParticipantRtcInit: perSec(earlier.ParticipantRtcInit, later.ParticipantRtcInit, elapsed), + ParticipantRtcConnected: perSec(earlier.ParticipantRtcConnected, later.ParticipantRtcConnected, elapsed), + ParticipantRtcCanceled: perSec(earlier.ParticipantRtcCanceled, later.ParticipantRtcCanceled, elapsed), + ParticipantRtcActive: perSec(earlier.ParticipantRtcActive, later.ParticipantRtcActive, elapsed), + SysPacketsOut: perSec(uint64(earlier.SysPacketsOut), uint64(later.SysPacketsOut), elapsed), + SysPacketsDropped: perSec(uint64(earlier.SysPacketsDropped), uint64(later.SysPacketsDropped), elapsed), + TrackPublishAttempts: perSec(uint64(earlier.NumTrackPublishAttempts), uint64(later.NumTrackPublishAttempts), elapsed), + TrackPublishSuccess: perSec(uint64(earlier.NumTrackPublishSuccess), uint64(later.NumTrackPublishSuccess), elapsed), + TrackPublishCancels: perSec(uint64(earlier.NumTrackPublishCancels), uint64(later.NumTrackPublishCancels), elapsed), + TrackSubscribeAttempts: perSec(uint64(earlier.NumTrackSubscribeAttempts), uint64(later.NumTrackSubscribeAttempts), elapsed), + TrackSubscribeSuccess: perSec(uint64(earlier.NumTrackSubscribeSuccess), uint64(later.NumTrackSubscribeSuccess), elapsed), + TrackSubscribeCancels: perSec(uint64(earlier.NumTrackSubscribeCancels), uint64(later.NumTrackSubscribeCancels), elapsed), + CpuLoad: cpuLoad / float32(elapsed), + MemoryLoad: memoryLoad / float32(elapsed), + MemoryUsed: memoryUsed / float32(elapsed), + MemoryTotal: memoryTotal / float32(elapsed), } return rate } diff --git a/pkg/telemetry/prometheus/packets.go b/pkg/telemetry/prometheus/packets.go index d8ac65610..a9f6684e1 100644 --- a/pkg/telemetry/prometheus/packets.go +++ b/pkg/telemetry/prometheus/packets.go @@ -36,19 +36,22 @@ const ( ) var ( - bytesIn atomic.Uint64 - bytesOut atomic.Uint64 - packetsIn atomic.Uint64 - packetsOut atomic.Uint64 - nackTotal atomic.Uint64 - retransmitBytes atomic.Uint64 - retransmitPackets atomic.Uint64 - participantSignalConnected atomic.Uint64 - participantRTCConnected atomic.Uint64 - participantRTCInit atomic.Uint64 - participantRTCCanceled atomic.Uint64 - forwardLatency atomic.Uint32 - forwardJitter atomic.Uint32 + bytesIn atomic.Uint64 + bytesOut atomic.Uint64 + packetsIn atomic.Uint64 + packetsOut atomic.Uint64 + nackTotal atomic.Uint64 + retransmitBytes atomic.Uint64 + retransmitPackets atomic.Uint64 + participantSignalConnected atomic.Uint64 + participantSignalFailed atomic.Uint64 + participantSignalValidationFailed atomic.Uint64 + participantRTCConnected atomic.Uint64 + participantRTCInit atomic.Uint64 + participantRTCCanceled atomic.Uint64 + participantRTCActive atomic.Uint64 + forwardLatency atomic.Uint32 + forwardJitter atomic.Uint32 promPacketLabels = []string{"direction", "transmission", "country"} promPacketTotal *prometheus.CounterVec @@ -300,9 +303,17 @@ func IncrementParticipantJoin(join uint32) { } } -func IncrementParticipantJoinFail(join uint32) { - if join > 0 { - promParticipantJoin.WithLabelValues("signal_failed").Add(float64(join)) +func IncrementParticipantJoinFail(fail uint32) { + if fail > 0 { + participantSignalFailed.Add(uint64(fail)) + promParticipantJoin.WithLabelValues("signal_failed").Add(float64(fail)) + } +} + +func IncrementParticipantJoinValidationFail(validationFail uint32) { + if validationFail > 0 { + participantSignalValidationFailed.Add(uint64(validationFail)) + promParticipantJoin.WithLabelValues("signal_validation_failed").Add(float64(validationFail)) } } @@ -320,9 +331,16 @@ func IncrementParticipantRtcConnected(join uint32) { } } +func IncrementParticipantRtcActive(active uint32) { + if active > 0 { + participantRTCActive.Add(uint64(active)) + promParticipantJoin.WithLabelValues("rtc_active").Add(float64(active)) + } +} + func IncrementParticipantRtcCanceled(numCancels uint64) { if numCancels > 0 { - participantRTCConnected.Add(numCancels) + participantRTCCanceled.Add(numCancels) promParticipantJoin.WithLabelValues("rtc_canceled").Add(float64(numCancels)) } } diff --git a/pkg/telemetry/prometheus/rooms.go b/pkg/telemetry/prometheus/rooms.go index 00ec4b11c..4001c24d9 100644 --- a/pkg/telemetry/prometheus/rooms.go +++ b/pkg/telemetry/prometheus/rooms.go @@ -49,6 +49,8 @@ var ( promSessionStartTime *prometheus.HistogramVec promSessionDuration *prometheus.HistogramVec promPubSubTime *prometheus.HistogramVec + + promPeerConnection *prometheus.CounterVec ) func initRoomStats(nodeID string, nodeType livekit.NodeType) { @@ -118,6 +120,12 @@ func initRoomStats(nodeID string, nodeType livekit.NodeType) { ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()}, Buckets: []float64{100, 200, 500, 700, 1000, 5000, 10000}, }, append(promStreamLabels, "sdk", "kind", "count")) + promPeerConnection = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: livekitNamespace, + Subsystem: "peer_connection", + Name: "state", + ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()}, + }, []string{"transport", "state"}) prometheus.MustRegister(promRoomCurrent) prometheus.MustRegister(promRoomDuration) @@ -129,6 +137,7 @@ func initRoomStats(nodeID string, nodeType livekit.NodeType) { prometheus.MustRegister(promSessionStartTime) prometheus.MustRegister(promSessionDuration) prometheus.MustRegister(promPubSubTime) + prometheus.MustRegister(promPeerConnection) } func RoomStarted() { @@ -269,3 +278,7 @@ func RecordSessionStartTime(protocolVersion int, d time.Duration) { func RecordSessionDuration(protocolVersion int, d time.Duration) { promSessionDuration.WithLabelValues(strconv.Itoa(protocolVersion)).Observe(float64(d.Milliseconds())) } + +func RecordPeerConnectionState(transport livekit.SignalTarget, state string) { + promPeerConnection.WithLabelValues(transport.String(), state).Inc() +} diff --git a/test/singlenode_test.go b/test/singlenode_test.go index bf51a04da..8a9de9062 100644 --- a/test/singlenode_test.go +++ b/test/singlenode_test.go @@ -765,13 +765,21 @@ func TestSingleNodeUpdateSubscriptionPermissions(t *testing.T) { SetIdentity("sub") token, err := at.ToJWT() require.NoError(t, err) - sub := createRTCClientWithToken(token, defaultServerPort, testRTCServicePath, nil) + sub := createRTCClientWithToken(token, defaultServerPort, testRTCServicePath, &testclient.Options{ + AutoSubscribe: true, + AutoSubscribeDataTrack: true, + }) waitUntilConnected(t, pub, sub) writers := publishTracksForClients(t, pub) defer stopWriters(writers...) + // publish a data track as well + dtw, err := pub.PublishDataTrack() + require.NoError(t, err) + defer dtw.Stop() + // wait sub receives tracks testutils.WithTimeout(t, func() string { pubRemote := sub.GetRemoteParticipant(pub.ID()) @@ -784,6 +792,10 @@ func TestSingleNodeUpdateSubscriptionPermissions(t *testing.T) { return "" }) + // no subscriptions should have been made while canSubscribe is false + require.Empty(t, sub.SubscribedTracks()[pub.ID()]) + require.Empty(t, sub.SubscribedDataTracks()[pub.ID()]) + // set permissions out of band ctx := contextWithToken(adminRoomToken(testRoom)) _, err = roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{ @@ -798,11 +810,13 @@ func TestSingleNodeUpdateSubscriptionPermissions(t *testing.T) { testutils.WithTimeout(t, func() string { tracks := sub.SubscribedTracks()[pub.ID()] - if len(tracks) == 2 { - return "" - } else { + if len(tracks) != 2 { return fmt.Sprintf("expected 2 tracks subscribed, actual: %d", len(tracks)) } + if len(sub.SubscribedDataTracks()[pub.ID()]) != 1 { + return "expected data track to be subscribed" + } + return "" }) }) } diff --git a/version/version.go b/version/version.go index 0abeb937f..fbc9dc1e2 100644 --- a/version/version.go +++ b/version/version.go @@ -14,4 +14,4 @@ package version -const Version = "1.12.0" +const Version = "1.13.1"