From a30129f07c37995786801e730e9604de60ffd637 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 4 May 2026 15:04:02 +0300 Subject: [PATCH 1/2] Allow specifying codecs for the SDP. --- .github/workflows/docker.yaml | 2 +- .github/workflows/test.yaml | 2 +- build/sip/Dockerfile | 2 +- go.mod | 36 ++++++++--------- go.sum | 76 +++++++++++++++++------------------ pkg/service/psrpc.go | 23 ++++++----- pkg/sip/client.go | 5 ++- pkg/sip/inbound.go | 31 +++++++------- pkg/sip/media_codecs.go | 62 ++++++++++++++++++++++++++-- pkg/sip/media_port.go | 12 +++--- pkg/sip/media_port_test.go | 33 ++++++--------- pkg/sip/outbound.go | 7 ++-- pkg/sip/server.go | 2 +- pkg/sip/service_test.go | 12 +++--- pkg/sip/signaling_test.go | 8 ++-- pkg/sip/types.go | 4 +- 16 files changed, 185 insertions(+), 132 deletions(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index c7a22d28d..ec7846fac 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -51,7 +51,7 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 with: - go-version: "1.25" + go-version: ">=1.26" - name: Download Go modules run: go mod download diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7b43a8d05..fbac7628d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,7 +35,7 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 with: - go-version: ">=1.25" + go-version: ">=1.26" - name: Set up tparse run: go install github.com/mfridman/tparse@v0.17.0 diff --git a/build/sip/Dockerfile b/build/sip/Dockerfile index a7b26c3a9..02d0bf0ed 100644 --- a/build/sip/Dockerfile +++ b/build/sip/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ARG GOVERSION=1.25 +ARG GOVERSION=1.26 FROM golang:$GOVERSION AS builder diff --git a/go.mod b/go.mod index 09829cb62..5c6e59124 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/livekit/sip -go 1.25.0 +go 1.26 require ( github.com/at-wat/ebml-go v0.17.1 @@ -9,9 +9,9 @@ require ( github.com/icholy/digest v1.1.0 github.com/jfreymuth/oggvorbis v1.0.5 github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 - github.com/livekit/media-sdk v0.0.0-20260408112950-ffe36f66afe6 + github.com/livekit/media-sdk v0.0.0-20260504125714-dd334c543809 github.com/livekit/mediatransportutil v0.0.0-20260309115634-0e2e24b36ee8 - github.com/livekit/protocol v1.45.2-0.20260403151849-8a360e8d0221 + github.com/livekit/protocol v1.45.8-0.20260506075942-ff4d6b682dc7 github.com/livekit/psrpc v0.7.1 github.com/livekit/server-sdk-go/v2 v2.16.3-0.20260422031603-fe7af253c41e github.com/livekit/sipgo v0.13.2-0.20260407210901-862b5e0eaf3f @@ -24,8 +24,8 @@ require ( github.com/prometheus/client_golang v1.22.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/trace v1.40.0 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a google.golang.org/protobuf v1.36.11 gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 @@ -74,7 +74,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gotranspile/g722 v0.0.0-20240123003956-384a1bb16a19 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/jfreymuth/vorbis v1.0.2 // indirect github.com/jxskiss/base62 v1.1.0 // indirect github.com/klauspost/compress v1.18.4 // indirect @@ -123,23 +123,23 @@ require ( github.com/zeebo/xxh3 v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.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/proto/otlp v1.10.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.1 // 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 ) diff --git a/go.sum b/go.sum index 2994128fd..958d32dbe 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gotranspile/g722 v0.0.0-20240123003956-384a1bb16a19 h1:vqA29ogkaaq2GxFQsMA8TTFUSGc1lGaZtnKbuiP840c= github.com/gotranspile/g722 v0.0.0-20240123003956-384a1bb16a19/go.mod h1:AcVi4yM6DRZscpQXsEWBPItD52Saqw0x7md4mmjzUi8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= @@ -132,12 +132,12 @@ github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkI 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/media-sdk v0.0.0-20260408112950-ffe36f66afe6 h1:CJtOPeFaXB9YxNH3PTVqImgNhSDtFDo6ww3Hmx3ErWc= -github.com/livekit/media-sdk v0.0.0-20260408112950-ffe36f66afe6/go.mod h1:7ssWiG+U4xnbvLih9WiZbhQP6zIKMjgXdUtIE1bm/E8= +github.com/livekit/media-sdk v0.0.0-20260504125714-dd334c543809 h1:h0bfcFCXX3UjTnZ0bKR5c3XX+8jBEAQQ9LG30yfVnyc= +github.com/livekit/media-sdk v0.0.0-20260504125714-dd334c543809/go.mod h1:7ssWiG+U4xnbvLih9WiZbhQP6zIKMjgXdUtIE1bm/E8= github.com/livekit/mediatransportutil v0.0.0-20260309115634-0e2e24b36ee8 h1:coWig9fKxdb/nwOaIoGUUAogso12GblAJh/9SA9hcxk= github.com/livekit/mediatransportutil v0.0.0-20260309115634-0e2e24b36ee8/go.mod h1:RCd46PT+6sEztld6XpkCrG1xskb0u3SqxIjy4G897Ss= -github.com/livekit/protocol v1.45.2-0.20260403151849-8a360e8d0221 h1:loe7h+z1kOu/ojprFTYSZBbJVly7gdZgQ/ewElGeLPo= -github.com/livekit/protocol v1.45.2-0.20260403151849-8a360e8d0221/go.mod h1:e6QdWDkfot+M2nRh0eitJUS0ZLuwvKCsfiz2pWWSG3s= +github.com/livekit/protocol v1.45.8-0.20260506075942-ff4d6b682dc7 h1:MR+ZMMPFas+H0WXg4N1WdSZwhDByqz16/Ayh+Tuc9XE= +github.com/livekit/protocol v1.45.8-0.20260506075942-ff4d6b682dc7/go.mod h1:KEPIJ/ZdMFQ9tmmfv/uT9TjQEuEcZupCZBabuRGEC1k= 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/server-sdk-go/v2 v2.16.3-0.20260422031603-fe7af253c41e h1:lRPGKCSkcc70swszvo9GR9pGqLGkb4zPPlyeDCIcBJk= @@ -264,22 +264,22 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +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/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= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -295,8 +295,8 @@ 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-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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +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-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -308,8 +308,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -326,8 +326,8 @@ 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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.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= @@ -339,8 +339,8 @@ 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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -348,14 +348,14 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn 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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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/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= diff --git a/pkg/service/psrpc.go b/pkg/service/psrpc.go index 8566b1b14..6f582ba85 100644 --- a/pkg/service/psrpc.go +++ b/pkg/service/psrpc.go @@ -90,6 +90,7 @@ func DispatchCall(ctx context.Context, psrpcClient rpc.IOInfoClient, log logger. log.Warnw("SIP handle dispatch rule error", err) return sip.CallDispatch{Result: sip.DispatchServiceUnavailable} } + resp.Upgrade() switch resp.Result { default: log.Errorw("SIP handle dispatch rule error", fmt.Errorf("unexpected dispatch result: %v", resp.Result)) @@ -101,11 +102,11 @@ func DispatchCall(ctx context.Context, psrpcClient rpc.IOInfoClient, log logger. case rpc.SIPDispatchResult_LEGACY_ACCEPT_OR_PIN: if resp.RequestPin { return sip.CallDispatch{ - ProjectID: resp.ProjectId, - TrunkID: resp.SipTrunkId, - DispatchRuleID: resp.SipDispatchRuleId, - Result: sip.DispatchRequestPin, - MediaEncryption: resp.MediaEncryption, + ProjectID: resp.ProjectId, + TrunkID: resp.SipTrunkId, + DispatchRuleID: resp.SipDispatchRuleId, + Result: sip.DispatchRequestPin, + MediaConfig: resp.Media, } } // TODO: finally deprecate and drop @@ -134,7 +135,7 @@ func DispatchCall(ctx context.Context, psrpcClient rpc.IOInfoClient, log logger. EnabledFeatures: resp.EnabledFeatures, RingingTimeout: resp.RingingTimeout.AsDuration(), MaxCallDuration: resp.MaxCallDuration.AsDuration(), - MediaEncryption: resp.MediaEncryption, + MediaConfig: resp.Media, } case rpc.SIPDispatchResult_ACCEPT: return sip.CallDispatch{ @@ -163,14 +164,14 @@ func DispatchCall(ctx context.Context, psrpcClient rpc.IOInfoClient, log logger. FeatureFlags: resp.FeatureFlags, RingingTimeout: resp.RingingTimeout.AsDuration(), MaxCallDuration: resp.MaxCallDuration.AsDuration(), - MediaEncryption: resp.MediaEncryption, + MediaConfig: resp.Media, } case rpc.SIPDispatchResult_REQUEST_PIN: return sip.CallDispatch{ - ProjectID: resp.ProjectId, - Result: sip.DispatchRequestPin, - TrunkID: resp.SipTrunkId, - MediaEncryption: resp.MediaEncryption, + ProjectID: resp.ProjectId, + Result: sip.DispatchRequestPin, + TrunkID: resp.SipTrunkId, + MediaConfig: resp.Media, } case rpc.SIPDispatchResult_REJECT: return sip.CallDispatch{ diff --git a/pkg/sip/client.go b/pkg/sip/client.go index 13b384184..0c3098f85 100644 --- a/pkg/sip/client.go +++ b/pkg/sip/client.go @@ -179,6 +179,7 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea if c.mon.Health() != stats.HealthOK { return nil, siperrors.ErrUnavailable } + req.Upgrade() if req.CallTo == "" { return nil, psrpc.NewErrorf(psrpc.InvalidArgument, "call-to number must be set") } else if req.Address == "" { @@ -207,7 +208,7 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea if req.SipTrunkId != "" { log = log.WithValues("sipTrunk", req.SipTrunkId) } - enc, err := sdpEncryption(req.MediaEncryption) + mconf, err := newMediaConfig(req.Media) if err != nil { return nil, err } @@ -271,7 +272,7 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea maxCallDuration: req.MaxCallDuration.AsDuration(), enabledFeatures: req.EnabledFeatures, featureFlags: req.FeatureFlags, - mediaEncryption: enc, + mediaConfig: mconf, displayName: req.DisplayName, } log.Infow("Creating SIP participant") diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 56865ef73..7441447b8 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -716,6 +716,9 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip NoPin: false, }) tdisp() + if disp.MediaConfig == nil { + disp.MediaConfig = &livekit.SIPMediaConfig{} + } if disp.ProjectID != "" { c.appendLogValues("projectID", disp.ProjectID) c.projectID = disp.ProjectID @@ -733,7 +736,7 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip info.RoomName = disp.Room.RoomName info.ParticipantIdentity = disp.Room.Participant.Identity info.ParticipantAttributes = disp.Room.Participant.Attributes - info.MediaEncryption = disp.MediaEncryption.String() + info.MediaEncryption = disp.MediaConfig.GetEncryption().String() info.EnabledFeatures = disp.EnabledFeatures // Set callidfull in participant attributes for backwards compatibility if c.call.SipCallId != "" { @@ -773,7 +776,7 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip pinPrompt = true } - runMedia := func(enc livekit.SIPMediaEncryption) ([]byte, error) { + runMedia := func(m *livekit.SIPMediaConfig) ([]byte, error) { log := c.log() if h := req.ContentLength(); h != nil { log = log.WithValues("contentLength", int(*h)) @@ -790,7 +793,7 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip } rawSDP := req.Body() tmedia := c.mon.StageDurTimer("start-media") - answerData, err := c.runMediaConn(tid, rawSDP, enc, conf, disp.EnabledFeatures, disp.FeatureFlags) + answerData, err := c.runMediaConn(tid, rawSDP, m, conf, disp.EnabledFeatures, disp.FeatureFlags) tmedia() if err != nil { sipReason := sip.StatusInternalServerError @@ -868,7 +871,7 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip // Accept the call first on the SIP side, so that we can send audio prompts. // This also means we have to pick encryption setting early, before room is selected. // Backend must explicitly enable encryption for pin prompts. - answerData, err = runMedia(disp.MediaEncryption) + answerData, err = runMedia(disp.MediaConfig) if err != nil { return err // already sent a response } @@ -882,7 +885,7 @@ func (c *inboundCall) handleInvite(ctx context.Context, tid traceid.ID, req *sip } else { // Start media with given encryption settings. var err error - answerData, err = runMedia(disp.MediaEncryption) + answerData, err = runMedia(disp.MediaConfig) if err != nil { return err // already sent a response } @@ -982,12 +985,12 @@ func (c *inboundCall) waitForCallEnd(ctx context.Context, ackReceived <-chan str } } -func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, enc livekit.SIPMediaEncryption, conf *config.Config, features []livekit.SIPFeature, featureFlags map[string]string) (answerData []byte, _ error) { +func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, m *livekit.SIPMediaConfig, conf *config.Config, features []livekit.SIPFeature, featureFlags map[string]string) (answerData []byte, _ error) { c.mon.SDPSize(len(offerData), true) c.log().Debugw("SDP offer", "sdp", string(offerData)) - e, err := sdpEncryption(enc) + mconf, err := newMediaConfig(m) if err != nil { - c.log().Errorw("Cannot parse encryption", err) + c.log().Errorw("Cannot create media config", err) return nil, err } @@ -1012,7 +1015,7 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, enc livekit c.media.DisableOut() // disabled until we send 200 c.media.SetDTMFAudio(conf.AudioDTMF) - answer, mconf, err := mp.SetOffer(offerData, e) + answer, mc, err := mp.SetOffer(offerData, mconf.Codecs, mconf.Encryption) if err != nil { return nil, err } @@ -1023,11 +1026,11 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, enc livekit c.mon.SDPSize(len(answerData), false) c.log().Debugw("SDP answer", "sdp", string(answerData)) - if err = c.media.SetConfig(mconf); err != nil { + if err = c.media.SetConfig(mc); err != nil { return nil, err } - mconf.Processor = c.s.handler.GetMediaProcessor(features, featureFlags, string(c.cc.ID()), MediaProcessorOpts{InputSampleRate: c.media.InputSampleRate()}) - if mconf.Audio.DTMFType != 0 { + mc.Processor = c.s.handler.GetMediaProcessor(features, featureFlags, string(c.cc.ID()), MediaProcessorOpts{InputSampleRate: c.media.InputSampleRate()}) + if mc.Audio.DTMFType != 0 { c.media.HandleDTMF(c.handleDTMF) } @@ -1035,11 +1038,11 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, enc livekit if w := c.lkRoom.SwapOutput(c.media.GetAudioWriter()); w != nil { _ = w.Close() } - if mconf.Audio.DTMFType != 0 { + if mc.Audio.DTMFType != 0 { c.lkRoom.SetDTMFOutput(c.media) } c.state.DeferUpdate(func(info *livekit.SIPCallInfo) { - info.AudioCodec = mconf.Audio.Codec.Info().SDPName + info.AudioCodec = mc.Audio.Codec.Info().SDPName }) return answerData, nil } diff --git a/pkg/sip/media_codecs.go b/pkg/sip/media_codecs.go index ce8ee01ae..a378d1f0d 100644 --- a/pkg/sip/media_codecs.go +++ b/pkg/sip/media_codecs.go @@ -16,7 +16,63 @@ package sip // Register supported audio codecs import ( - _ "github.com/livekit/media-sdk/dtmf" - _ "github.com/livekit/media-sdk/g711" - _ "github.com/livekit/media-sdk/g722" + "errors" + "fmt" + + _ "github.com/livekit/media-sdk/all" + "github.com/livekit/media-sdk/dtmf" + "github.com/livekit/media-sdk/g711" + "github.com/livekit/media-sdk/g722" + "github.com/livekit/media-sdk/sdp" + + msdk "github.com/livekit/media-sdk" + "github.com/livekit/protocol/livekit" ) + +var defaultCodecs = msdk.NewCodecSet() + +func init() { + defaultCodecs.SetEnabledMap(map[string]bool{ + g711.ALawSDPName: true, + g711.ULawSDPName: true, + g722.SDPName: true, + dtmf.SDPName: true, + }) +} + +func newMediaConfig(m *livekit.SIPMediaConfig) (*sipMediaConfig, error) { + enc, err := sdpEncryption(m.Encryption) + if err != nil { + return nil, err + } + s, err := codecSet(m) + if err != nil { + return nil, err + } + return &sipMediaConfig{ + Encryption: enc, + Codecs: s, + }, nil +} + +type sipMediaConfig struct { + Encryption sdp.Encryption + Codecs *msdk.CodecSet +} + +func codecSet(m *livekit.SIPMediaConfig) (*msdk.CodecSet, error) { + var s *msdk.CodecSet + if m.OnlyListedCodecs { + if len(m.Codecs) == 0 { + return nil, errors.New("no codecs specified") + } + s = msdk.NewCodecSet() // empty set + } else { + s = defaultCodecs.NewSet() // inherit from default + } + for _, codec := range m.Codecs { + name := fmt.Sprintf("%s/%d", codec.Name, codec.Rate) + s.SetEnabled(name, true) + } + return s, nil +} diff --git a/pkg/sip/media_port.go b/pkg/sip/media_port.go index aefae67c7..eda77074d 100644 --- a/pkg/sip/media_port.go +++ b/pkg/sip/media_port.go @@ -634,14 +634,14 @@ func (p *MediaPort) GetAudioWriter() msdk.PCM16Writer { } // NewOffer generates an SDP offer for the media. -func (p *MediaPort) NewOffer(encrypted sdp.Encryption) (*sdp.Offer, error) { - return sdp.NewOffer(p.externalIP, p.Port(), encrypted) +func (p *MediaPort) NewOffer(codecs *msdk.CodecSet, encrypted sdp.Encryption) (*sdp.Offer, error) { + return sdp.NewOfferWith(codecs, p.externalIP, p.Port(), encrypted) } // SetAnswer decodes and applies SDP answer for offer from NewOffer. // SetConfig must be called with the decoded configuration. -func (p *MediaPort) SetAnswer(offer *sdp.Offer, answerData []byte, enc sdp.Encryption) (*MediaConf, []byte, error) { - answer, err := sdp.ParseAnswer(answerData) +func (p *MediaPort) SetAnswer(offer *sdp.Offer, answerData []byte, codecs *msdk.CodecSet, enc sdp.Encryption) (*MediaConf, []byte, error) { + answer, err := sdp.ParseAnswerWith(codecs, answerData) if err != nil { return nil, nil, SDPError{Err: err} } @@ -657,8 +657,8 @@ func (p *MediaPort) SetAnswer(offer *sdp.Offer, answerData []byte, enc sdp.Encry } // SetOffer decodes the offer from another party and returns encoded answer. To accept the offer, call SetConfig. -func (p *MediaPort) SetOffer(offerData []byte, enc sdp.Encryption) (*sdp.Answer, *MediaConf, error) { - offer, err := sdp.ParseOffer(offerData) +func (p *MediaPort) SetOffer(offerData []byte, codecs *msdk.CodecSet, enc sdp.Encryption) (*sdp.Answer, *MediaConf, error) { + offer, err := sdp.ParseOfferWith(codecs, offerData) if err != nil { return nil, nil, SDPError{Err: err} } diff --git a/pkg/sip/media_port_test.go b/pkg/sip/media_port_test.go index d87cec7a0..c5953e0f9 100644 --- a/pkg/sip/media_port_test.go +++ b/pkg/sip/media_port_test.go @@ -165,23 +165,12 @@ func newIP(v string) netip.Addr { } func TestMediaPort(t *testing.T) { - codecs := msdk.Codecs() - disableAll := func() { - for _, codec := range codecs { - msdk.CodecSetEnabled(codec.Info().SDPName, false) - } - } - defer func() { - for _, codec := range codecs { - info := codec.Info() - msdk.CodecSetEnabled(info.SDPName, !info.Disabled) - } - }() - for _, codec := range codecs { + codecList := msdk.Codecs() + for _, codec := range codecList { info := codec.Info() t.Run(info.SDPName, func(t *testing.T) { - disableAll() - msdk.CodecSetEnabled(info.SDPName, true) + codecs := msdk.NewCodecSet() + codecs.SetEnabled(info.SDPName, true) sub := strings.SplitN(info.SDPName, "/", 2) name := sub[0] @@ -232,21 +221,21 @@ func TestMediaPort(t *testing.T) { require.NoError(t, err) defer m2.Close() - offer, err := m1.NewOffer(tconf.Encrypted) + offer, err := m1.NewOffer(codecs, tconf.Encrypted) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) t.Logf("SDP offer:\n%s", string(offerData)) - answer, conf, err := m2.SetOffer(offerData, tconf.Encrypted) + answer, conf, err := m2.SetOffer(offerData, codecs, tconf.Encrypted) require.NoError(t, err) answerData, err := answer.SDP.Marshal() require.NoError(t, err) t.Logf("SDP answer:\n%s", string(answerData)) - mc, _, err := m1.SetAnswer(offer, answerData, tconf.Encrypted) + mc, _, err := m1.SetAnswer(offer, answerData, codecs, tconf.Encrypted) require.NoError(t, err) err = m1.SetConfig(mc) @@ -375,6 +364,8 @@ func newMediaPair(t testing.TB, opt1, opt2 *MediaOptions) (m1, m2 *MediaPort) { } c1, c2 := newUDPPipe() + codecs := defaultCodecs + opt1.IP = newIP("1.1.1.1") opt1.Ports = rtcconfig.PortRange{Start: 10000} opt1.NoInputResample = true @@ -396,17 +387,17 @@ func newMediaPair(t testing.TB, opt1, opt2 *MediaOptions) (m1, m2 *MediaPort) { require.NoError(t, err) t.Cleanup(m2.Close) - offer, err := m1.NewOffer(sdp.EncryptionNone) + offer, err := m1.NewOffer(codecs, sdp.EncryptionNone) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) - answer, mc2, err := m2.SetOffer(offerData, sdp.EncryptionNone) + answer, mc2, err := m2.SetOffer(offerData, codecs, sdp.EncryptionNone) require.NoError(t, err) answerData, err := answer.SDP.Marshal() require.NoError(t, err) - mc1, _, err := m1.SetAnswer(offer, answerData, sdp.EncryptionNone) + mc1, _, err := m1.SetAnswer(offer, answerData, codecs, sdp.EncryptionNone) require.NoError(t, err) err = m1.SetConfig(mc1) diff --git a/pkg/sip/outbound.go b/pkg/sip/outbound.go index 5c8bf1d55..cc56337f9 100644 --- a/pkg/sip/outbound.go +++ b/pkg/sip/outbound.go @@ -64,7 +64,7 @@ type sipOutboundConfig struct { maxCallDuration time.Duration enabledFeatures []livekit.SIPFeature featureFlags map[string]string - mediaEncryption sdp.Encryption + mediaConfig *sipMediaConfig displayName *string } @@ -603,7 +603,8 @@ func (c *outboundCall) sipSignal(ctx context.Context, tid traceid.ID) error { cancel() }() - sdpOffer, err := c.media.NewOffer(c.sipConf.mediaEncryption) + mconf := c.sipConf.mediaConfig + sdpOffer, err := c.media.NewOffer(mconf.Codecs, mconf.Encryption) if err != nil { return err } @@ -667,7 +668,7 @@ func (c *outboundCall) sipSignal(ctx context.Context, tid traceid.ID) error { c.log = LoggerWithHeaders(c.log, c.cc) - mc, localSDP, err := c.media.SetAnswer(sdpOffer, sdpResp, c.sipConf.mediaEncryption) + mc, localSDP, err := c.media.SetAnswer(sdpOffer, sdpResp, mconf.Codecs, mconf.Encryption) if err != nil { return err } diff --git a/pkg/sip/server.go b/pkg/sip/server.go index fdd1b87f4..9b3c33841 100644 --- a/pkg/sip/server.go +++ b/pkg/sip/server.go @@ -108,7 +108,7 @@ type CallDispatch struct { FeatureFlags map[string]string RingingTimeout time.Duration MaxCallDuration time.Duration - MediaEncryption livekit.SIPMediaEncryption + MediaConfig *livekit.SIPMediaConfig } type CallIdentifier struct { diff --git a/pkg/sip/service_test.go b/pkg/sip/service_test.go index a4b3e47d2..0bdc94e83 100644 --- a/pkg/sip/service_test.go +++ b/pkg/sip/service_test.go @@ -160,7 +160,7 @@ func testInvite(t *testing.T, h Handler, hidden bool, from, to string, test func sipClient, err := sipgo.NewClient(sipUserAgent) require.NoError(t, err) - offer, err := sdp.NewOffer(localIP, 0xB0B, sdp.EncryptionNone) + offer, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0B, sdp.EncryptionNone) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) @@ -419,12 +419,12 @@ func TestDigestAuthSimultaneousCalls(t *testing.T) { require.NoError(t, err) // Create SDP offers - offer1, err := sdp.NewOffer(localIP, 0xB0B, sdp.EncryptionNone) + offer1, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0B, sdp.EncryptionNone) require.NoError(t, err) offerData1, err := offer1.SDP.Marshal() require.NoError(t, err) - offer2, err := sdp.NewOffer(localIP, 0xB0C, sdp.EncryptionNone) + offer2, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0C, sdp.EncryptionNone) require.NoError(t, err) offerData2, err := offer2.SDP.Marshal() require.NoError(t, err) @@ -613,7 +613,7 @@ func TestDigestAuthStandardFlow(t *testing.T) { sipClient, err := sipgo.NewClient(sipUserAgent) require.NoError(t, err) - offer, err := sdp.NewOffer(localIP, 0xB0B, sdp.EncryptionNone) + offer, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0B, sdp.EncryptionNone) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) @@ -751,7 +751,7 @@ func TestCANCELSendsBothResponses(t *testing.T) { require.NoError(t, err) // Create SDP offer - offer, err := sdp.NewOffer(localIP, 0xB0B, sdp.EncryptionNone) + offer, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0B, sdp.EncryptionNone) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) @@ -918,7 +918,7 @@ func TestSameCallIDForAuthFlow(t *testing.T) { sipClient, err := sipgo.NewClient(sipUserAgent) require.NoError(t, err) - offer, err := sdp.NewOffer(localIP, 0xB0B, sdp.EncryptionNone) + offer, err := sdp.NewOfferWith(defaultCodecs, localIP, 0xB0B, sdp.EncryptionNone) require.NoError(t, err) offerData, err := offer.SDP.Marshal() require.NoError(t, err) diff --git a/pkg/sip/signaling_test.go b/pkg/sip/signaling_test.go index 329682417..954a4a612 100644 --- a/pkg/sip/signaling_test.go +++ b/pkg/sip/signaling_test.go @@ -369,7 +369,7 @@ func (d *sipUADialogTest) NewRequest(method sip.RequestMethod) *sip.Request { func (d *sipUADialogTest) Invite(offer []byte) (*sip.Request, []byte, error) { if offer == nil { - sdpOffer, err := sdp.NewOffer(d.TestUA.localAddr.Addr(), 0xB0B, sdp.EncryptionNone) + sdpOffer, err := sdp.NewOfferWith(defaultCodecs, d.TestUA.localAddr.Addr(), 0xB0B, sdp.EncryptionNone) if err != nil { return nil, nil, err } @@ -552,7 +552,7 @@ func (st *serviceTest) CreateOutboundCall(t *testing.T, opts ...createCallTestOp opt(msg.req, nil) // Simulate added headers } - offer, err := sdp.ParseOffer(msg.req.Body()) + offer, err := sdp.ParseOfferWith(defaultCodecs, msg.req.Body()) require.NoError(t, err) sdpAnswer, _, err := offer.Answer(netip.MustParseAddr("4.3.2.1"), 0xB00, sdp.EncryptionNone) require.NoError(t, err) @@ -608,7 +608,7 @@ func TestReinvite(t *testing.T) { require.Equal(t, serverLocalSDP, resp.Body(), "reinvite 200 OK should return server local SDP") // Re-INVITE with new offer - newOffer, err := sdp.NewOffer(netip.MustParseAddr("9.8.7.6"), 12345, sdp.EncryptionNone) + newOffer, err := sdp.NewOfferWith(defaultCodecs, netip.MustParseAddr("9.8.7.6"), 12345, sdp.EncryptionNone) require.NoError(t, err) newOfferBytes, err := newOffer.SDP.Marshal() require.NoError(t, err) @@ -655,7 +655,7 @@ func TestReinvite(t *testing.T) { require.Equal(t, serverLocalSDP, resp.Body(), "reinvite 200 OK should return server local SDP") // Re-INVITE with new offer - newOffer, err := sdp.NewOffer(netip.MustParseAddr("9.8.7.6"), 12345, sdp.EncryptionNone) + newOffer, err := sdp.NewOfferWith(defaultCodecs, netip.MustParseAddr("9.8.7.6"), 12345, sdp.EncryptionNone) require.NoError(t, err) newOfferBytes, err := newOffer.SDP.Marshal() require.NoError(t, err) diff --git a/pkg/sip/types.go b/pkg/sip/types.go index 2baec418a..a7862270b 100644 --- a/pkg/sip/types.go +++ b/pkg/sip/types.go @@ -297,8 +297,8 @@ func AttrsToHeaders(attrs, attrToHdr, headers map[string]string) map[string]stri return headers } -func sdpEncryption(e livekit.SIPMediaEncryption) (sdp.Encryption, error) { - switch e { +func sdpEncryption(e *livekit.SIPMediaEncryption) (sdp.Encryption, error) { + switch e.Deref() { case livekit.SIPMediaEncryption_SIP_MEDIA_ENCRYPT_DISABLE: return sdp.EncryptionNone, nil case livekit.SIPMediaEncryption_SIP_MEDIA_ENCRYPT_ALLOW: From ec00129f859815017a361a0a66cd6ad68fa27c41 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 4 May 2026 18:36:39 +0200 Subject: [PATCH 2/2] Prevent data race when closing media conn during init. --- pkg/sip/inbound.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 7441447b8..b95ad81bc 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -609,6 +609,7 @@ type inboundCall struct { cancel func() closeReason atomic.Pointer[ReasonHeader] call *rpc.SIPCall + mmu sync.Mutex media *MediaPort dtmf chan dtmf.Event // buffered lkRoom RoomInterface // LiveKit room; only active after correct pin is entered @@ -986,6 +987,8 @@ func (c *inboundCall) waitForCallEnd(ctx context.Context, ackReceived <-chan str } func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, m *livekit.SIPMediaConfig, conf *config.Config, features []livekit.SIPFeature, featureFlags map[string]string) (answerData []byte, _ error) { + c.mmu.Lock() + defer c.mmu.Unlock() c.mon.SDPSize(len(offerData), true) c.log().Debugw("SDP offer", "sdp", string(offerData)) mconf, err := newMediaConfig(m) @@ -1011,9 +1014,9 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, m *livekit. return nil, err } c.media = mp - c.media.EnableTimeout(false) // enabled once we accept the call - c.media.DisableOut() // disabled until we send 200 - c.media.SetDTMFAudio(conf.AudioDTMF) + mp.EnableTimeout(false) // enabled once we accept the call + mp.DisableOut() // disabled until we send 200 + mp.SetDTMFAudio(conf.AudioDTMF) answer, mc, err := mp.SetOffer(offerData, mconf.Codecs, mconf.Encryption) if err != nil { @@ -1026,20 +1029,20 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, m *livekit. c.mon.SDPSize(len(answerData), false) c.log().Debugw("SDP answer", "sdp", string(answerData)) - if err = c.media.SetConfig(mc); err != nil { + if err = mp.SetConfig(mc); err != nil { return nil, err } mc.Processor = c.s.handler.GetMediaProcessor(features, featureFlags, string(c.cc.ID()), MediaProcessorOpts{InputSampleRate: c.media.InputSampleRate()}) if mc.Audio.DTMFType != 0 { - c.media.HandleDTMF(c.handleDTMF) + mp.HandleDTMF(c.handleDTMF) } // Must be set earlier to send the pin prompts. - if w := c.lkRoom.SwapOutput(c.media.GetAudioWriter()); w != nil { + if w := c.lkRoom.SwapOutput(mp.GetAudioWriter()); w != nil { _ = w.Close() } if mc.Audio.DTMFType != 0 { - c.lkRoom.SetDTMFOutput(c.media) + c.lkRoom.SetDTMFOutput(mp) } c.state.DeferUpdate(func(info *livekit.SIPCallInfo) { info.AudioCodec = mc.Audio.Codec.Info().SDPName @@ -1318,6 +1321,8 @@ func (c *inboundCall) Shutdown(ctx context.Context) { func (c *inboundCall) closeMedia() { c.lkRoom.Close() + c.mmu.Lock() + defer c.mmu.Unlock() if c.media != nil { c.media.Close() }