@@ -16,15 +16,20 @@ package sip
1616
1717import (
1818 "context"
19+ "fmt"
1920 "log/slog"
21+ "net"
2022 "net/netip"
23+ "strconv"
2124 "strings"
2225 "sync"
2326 "time"
2427
2528 "github.com/frostbyte73/core"
2629 "golang.org/x/exp/maps"
2730
31+ esip "github.com/emiago/sipgo/sip"
32+
2833 "github.com/livekit/protocol/livekit"
2934 "github.com/livekit/protocol/logger"
3035 "github.com/livekit/protocol/rpc"
@@ -175,32 +180,168 @@ func (c *Client) getActiveCall(tag LocalTag) *outboundCall {
175180 return c .activeCalls [tag ]
176181}
177182
183+ func setUriTransport (p * sip.Uri , tr livekit.SIPTransport ) {
184+ if tr != livekit .SIPTransport_SIP_TRANSPORT_AUTO {
185+ p .UriParams .Add ("transport" , tr .Name ())
186+ }
187+ }
188+
189+ func buildLegacyURI (user , addr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
190+ if user == "" {
191+ return nil , fmt .Errorf ("number must be set" )
192+ } else if strings .Contains (user , "@" ) {
193+ return nil , fmt .Errorf ("should be a phone number or SIP user, not a full SIP URI" )
194+ }
195+ if addr == "" {
196+ return nil , fmt .Errorf ("address must be set" )
197+ }
198+ if strings .HasPrefix (addr , "sip:" ) || strings .HasPrefix (addr , "sips:" ) {
199+ return nil , fmt .Errorf ("address must be a hostname without 'sip:' prefix" )
200+ } else if strings .Contains (addr , "transport=" ) {
201+ return nil , fmt .Errorf ("legacy address must not contain parameters; use transport field" )
202+ } else if strings .ContainsAny (addr , ";=" ) {
203+ return nil , fmt .Errorf ("legacy address must not contain parameters" )
204+ }
205+ p := & sip.Uri {Scheme : "sip" }
206+ setUriTransport (p , tr )
207+
208+ p .User = user
209+ if host , sport , err := net .SplitHostPort (addr ); err == nil && sport != "" {
210+ p .Host = host
211+ p .Port , err = strconv .Atoi (sport )
212+ if err != nil {
213+ return nil , fmt .Errorf ("invalid port in hostname: %q" , sport )
214+ }
215+ } else {
216+ p .Host = addr
217+ }
218+ return p , nil
219+ }
220+
221+ func buildRawURI (raw string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
222+ p := & sip.Uri {Scheme : "sip" }
223+ setUriTransport (p , tr )
224+ if err := esip .ParseUri (raw , p ); err != nil {
225+ return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "invalid request URI" )
226+ }
227+ return p , nil
228+ }
229+
230+ func buildValuesURI (u * livekit.SIPUri , tr livekit.SIPTransport ) (* sip.Uri , error ) {
231+ if tr != u .Transport {
232+ if u .Transport == livekit .SIPTransport_SIP_TRANSPORT_AUTO {
233+ tr = tr
234+ } else if tr == livekit .SIPTransport_SIP_TRANSPORT_AUTO {
235+ tr = u .Transport
236+ } else {
237+ return nil , fmt .Errorf ("different transports specified: %v vs %v" , tr , u .Transport )
238+ }
239+ }
240+ p := & sip.Uri {Scheme : "sip" }
241+ setUriTransport (p , tr )
242+ if u .User == "" {
243+ return nil , fmt .Errorf ("username or number must be set" )
244+ }
245+ if u .Host == "" && u .Ip == "" {
246+ return nil , fmt .Errorf ("host or ip must be set" )
247+ }
248+ p .User = u .User
249+ p .Host = u .Host
250+ if p .Host == "" {
251+ p .Host = u .Ip
252+ }
253+ if _ , sport , err := net .SplitHostPort (p .Host ); err == nil && sport != "" {
254+ return nil , fmt .Errorf ("host or ip must not contain port" )
255+ }
256+ p .Port = int (u .Port )
257+ return p , nil
258+ }
259+
260+ func buildRequestURI (u * livekit.SIPRequestDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
261+ if u == nil {
262+ return buildLegacyURI (legacyUser , legacyAddr , tr )
263+ }
264+ switch u := u .Uri .(type ) {
265+ default :
266+ case * livekit.SIPRequestDest_Raw :
267+ return buildRawURI (u .Raw , tr )
268+ case * livekit.SIPRequestDest_Values :
269+ return buildValuesURI (u .Values , tr )
270+ }
271+ return nil , fmt .Errorf ("invalid request URI type" )
272+ }
273+
274+ func buildFromToURI (u * livekit.SIPNamedDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
275+ if u == nil {
276+ return buildLegacyURI (legacyUser , legacyAddr , tr )
277+ }
278+ switch u := u .Uri .(type ) {
279+ default :
280+ case * livekit.SIPNamedDest_Raw :
281+ return buildRawURI (u .Raw , tr )
282+ case * livekit.SIPNamedDest_Values :
283+ return buildValuesURI (u .Values , tr )
284+ }
285+ return nil , fmt .Errorf ("invalid URI type" )
286+ }
287+
288+ func buildFromHeader (u * livekit.SIPNamedDest , legacyName * string , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.FromHeader , error ) {
289+ su , err := buildFromToURI (u , legacyUser , legacyAddr , tr )
290+ if err != nil {
291+ return nil , err
292+ }
293+ h := & sip.FromHeader {
294+ Address : * su ,
295+ }
296+ if u != nil {
297+ h .DisplayName = u .DisplayName
298+ } else if legacyName != nil {
299+ h .DisplayName = * legacyName
300+ } else {
301+ // Nothing specified, preserve legacy behavior
302+ h .DisplayName = su .User
303+ }
304+ return h , nil
305+ }
306+
307+ func buildToHeader (u * livekit.SIPNamedDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.ToHeader , error ) {
308+ su , err := buildFromToURI (u , legacyUser , legacyAddr , tr )
309+ if err != nil {
310+ return nil , err
311+ }
312+ h := & sip.ToHeader {
313+ Address : * su ,
314+ }
315+ if u != nil {
316+ h .DisplayName = u .DisplayName
317+ }
318+ return h , nil
319+ }
320+
321+ func buildOutboundHeaders (req * rpc.InternalCreateSIPParticipantRequest ) (* sip.Uri , * sip.FromHeader , * sip.ToHeader , error ) {
322+ uri , err := buildRequestURI (req .SipRequestUri , req .CallTo , req .Address , req .Transport )
323+ if err != nil {
324+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid request URI: %w" , err ))
325+ }
326+ to , err := buildToHeader (req .SipToHeader , req .CallTo , req .Address , req .Transport )
327+ if err != nil {
328+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid To header: %w" , err ))
329+ }
330+ from , err := buildFromHeader (req .SipFromHeader , req .DisplayName , req .Number , req .Hostname , req .Transport )
331+ if err != nil {
332+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid From header: %w" , err ))
333+ }
334+ return uri , from , to , nil
335+ }
336+
178337func (c * Client ) createSIPParticipant (ctx context.Context , req * rpc.InternalCreateSIPParticipantRequest ) (resp * rpc.InternalCreateSIPParticipantResponse , retErr error ) {
179338 if c .mon .Health () != stats .HealthOK {
180339 return nil , siperrors .ErrUnavailable
181340 }
182341 req .Upgrade ()
183- if req .CallTo == "" {
184- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "call-to number must be set" )
185- } else if req .Address == "" {
186- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "trunk adresss must be set" )
187- } else if req .Number == "" {
188- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "trunk outbound number must be set" )
189- } else if req .RoomName == "" {
342+ if req .RoomName == "" {
190343 return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "room name must be set" )
191344 }
192- if strings .Contains (req .CallTo , "@" ) {
193- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "call_to should be a phone number or SIP user, not a full SIP URI" )
194- }
195- if strings .HasPrefix (req .Address , "sip:" ) || strings .HasPrefix (req .Address , "sips:" ) {
196- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "address must be a hostname without 'sip:' prefix" )
197- }
198- if strings .Contains (req .Address , "transport=" ) {
199- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "address must not contain parameters; use transport field" )
200- }
201- if strings .ContainsAny (req .Address , ";=" ) {
202- return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "address must not contain parameters" )
203- }
204345 log := c .log
205346 if req .ProjectId != "" {
206347 log = log .WithValues ("projectID" , req .ProjectId )
@@ -212,22 +353,28 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
212353 if err != nil {
213354 return nil , err
214355 }
356+ uri , from , to , err := buildOutboundHeaders (req )
357+ if err != nil {
358+ return nil , err
359+ }
215360 tid := traceid .FromGUID (req .SipCallId )
216361 log = log .WithValues (
217362 "callID" , req .SipCallId ,
218363 "traceID" , tid .String (),
219364 "room" , req .RoomName ,
220365 "participant" , req .ParticipantIdentity ,
221366 "participantName" , req .ParticipantName ,
222- "fromHost" , req .Hostname ,
223- "fromUser" , req .Number ,
224- "toHost" , req .Address ,
225- "toUser" , req .CallTo ,
367+ "fromHost" , from .Address .Host ,
368+ "fromUser" , from .Address .User ,
369+ "toHost" , to .Address .Host ,
370+ "toUser" , to .Address .User ,
371+ "reqHost" , uri .Host ,
372+ "reqUser" , uri .User ,
226373 "direction" , "outbound" ,
227374 )
228375
229376 req .ParticipantAttributes = maps .Clone (req .ParticipantAttributes ) // shallow clone - string/string map. Needed to avoid mutating psrpc req
230- state := NewCallState (c .getIOClient (req .ProjectId ), c .createSIPCallInfo (req ))
377+ state := NewCallState (c .getIOClient (req .ProjectId ), c .createSIPCallInfo (uri , from , to , req ))
231378
232379 defer func () {
233380 state .Update (ctx , func (info * livekit.SIPCallInfo ) {
@@ -255,11 +402,10 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
255402 },
256403 }
257404 sipConf := sipOutboundConfig {
258- address : req .Address ,
259405 transport : req .Transport ,
260- host : req . Hostname ,
261- from : req . Number ,
262- to : req . CallTo ,
406+ uri : uri ,
407+ from : from ,
408+ to : to ,
263409 user : req .Username ,
264410 pass : req .Password ,
265411 dtmf : req .Dtmf ,
@@ -273,7 +419,6 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
273419 enabledFeatures : req .EnabledFeatures ,
274420 featureFlags : req .FeatureFlags ,
275421 mediaConfig : mconf ,
276- displayName : req .DisplayName ,
277422 }
278423 log .Infow ("Creating SIP participant" )
279424 call , err := c .newCall (ctx , tid , c .conf , log , LocalTag (req .SipCallId ), roomConf , sipConf , state , req .ProjectId )
@@ -299,13 +444,10 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
299444 return info , nil
300445}
301446
302- func (c * Client ) createSIPCallInfo (req * rpc.InternalCreateSIPParticipantRequest ) * livekit.SIPCallInfo {
303- toUri := CreateURIFromUserAndAddress (req .CallTo , req .Address , TransportFrom (req .Transport ))
304- fromiUri := URI {
305- User : req .Number ,
306- Host : req .Hostname ,
307- Addr : netip .AddrPortFrom (c .sconf .SignalingIP , uint16 (c .conf .SIPPort )),
308- }
447+ func (c * Client ) createSIPCallInfo (uri * sip.Uri , from * sip.FromHeader , to * sip.ToHeader , req * rpc.InternalCreateSIPParticipantRequest ) * livekit.SIPCallInfo {
448+ toUri := ConvertURI (& to .Address )
449+ fromUri := ConvertURI (& from .Address )
450+ fromUri .Addr = netip .AddrPortFrom (c .sconf .SignalingIP , uint16 (c .conf .SIPPort ))
309451
310452 callInfo := & livekit.SIPCallInfo {
311453 CallId : req .SipCallId ,
@@ -316,7 +458,7 @@ func (c *Client) createSIPCallInfo(req *rpc.InternalCreateSIPParticipantRequest)
316458 ParticipantAttributes : req .ParticipantAttributes ,
317459 CallDirection : livekit .SIPCallDirection_SCD_OUTBOUND ,
318460 ToUri : toUri .ToSIPUri (),
319- FromUri : fromiUri .ToSIPUri (),
461+ FromUri : fromUri .ToSIPUri (),
320462 CreatedAtNs : time .Now ().UnixNano (),
321463 MediaEncryption : req .MediaEncryption .String (),
322464 EnabledFeatures : req .EnabledFeatures ,
0 commit comments