@@ -16,15 +16,21 @@ package sip
1616
1717import (
1818 "context"
19+ "errors"
20+ "fmt"
1921 "log/slog"
22+ "net"
2023 "net/netip"
24+ "strconv"
2125 "strings"
2226 "sync"
2327 "time"
2428
2529 "github.com/frostbyte73/core"
2630 "golang.org/x/exp/maps"
2731
32+ esip "github.com/emiago/sipgo/sip"
33+
2834 "github.com/livekit/protocol/livekit"
2935 "github.com/livekit/protocol/logger"
3036 "github.com/livekit/protocol/rpc"
@@ -175,32 +181,179 @@ func (c *Client) getActiveCall(tag LocalTag) *outboundCall {
175181 return c .activeCalls [tag ]
176182}
177183
184+ func setUriTransport (p * sip.Uri , tr livekit.SIPTransport ) {
185+ if tr != livekit .SIPTransport_SIP_TRANSPORT_AUTO {
186+ p .UriParams .Add ("transport" , tr .Name ())
187+ }
188+ }
189+
190+ func buildLegacyURI (user , addr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
191+ if user == "" {
192+ return nil , fmt .Errorf ("number must be set" )
193+ } else if strings .Contains (user , "@" ) {
194+ return nil , fmt .Errorf ("should be a phone number or SIP user, not a full SIP URI" )
195+ }
196+ if addr == "" {
197+ return nil , fmt .Errorf ("address must be set" )
198+ }
199+ if strings .HasPrefix (addr , "sip:" ) || strings .HasPrefix (addr , "sips:" ) {
200+ return nil , fmt .Errorf ("address must be a hostname without 'sip:' prefix" )
201+ } else if strings .Contains (addr , "transport=" ) {
202+ return nil , fmt .Errorf ("legacy address must not contain parameters; use transport field" )
203+ } else if strings .ContainsAny (addr , ";=" ) {
204+ return nil , fmt .Errorf ("legacy address must not contain parameters" )
205+ }
206+ p := & sip.Uri {Scheme : "sip" }
207+ setUriTransport (p , tr )
208+
209+ p .User = user
210+ if host , sport , err := net .SplitHostPort (addr ); err == nil && sport != "" {
211+ p .Host = host
212+ p .Port , err = strconv .Atoi (sport )
213+ if err != nil {
214+ return nil , fmt .Errorf ("invalid port in hostname: %q" , sport )
215+ }
216+ } else {
217+ p .Host = addr
218+ }
219+ return p , nil
220+ }
221+
222+ func buildRawURI (raw string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
223+ p := & sip.Uri {Scheme : "sip" }
224+ if n := len (raw ); n != 0 && raw [0 ] == '<' && raw [n - 1 ] == '>' {
225+ raw = raw [1 : n - 1 ]
226+ }
227+ if err := esip .ParseUri (raw , p ); err != nil {
228+ return nil , errors .New ("invalid request URI" )
229+ }
230+ setUriTransport (p , tr )
231+ return p , nil
232+ }
233+
234+ func buildValuesURI (u * livekit.SIPUri , tr livekit.SIPTransport ) (* sip.Uri , error ) {
235+ if tr != u .Transport {
236+ if u .Transport == livekit .SIPTransport_SIP_TRANSPORT_AUTO {
237+ //tr = tr
238+ } else if tr == livekit .SIPTransport_SIP_TRANSPORT_AUTO {
239+ tr = u .Transport
240+ } else {
241+ return nil , fmt .Errorf ("different transports specified: %v vs %v" , tr , u .Transport )
242+ }
243+ }
244+ p := & sip.Uri {Scheme : "sip" }
245+ setUriTransport (p , tr )
246+ if u .User == "" {
247+ return nil , fmt .Errorf ("username or number must be set" )
248+ }
249+ if u .Host == "" && u .Ip == "" {
250+ return nil , fmt .Errorf ("host or ip must be set" )
251+ }
252+ p .User = u .User
253+ p .Host = u .Host
254+ if p .Host == "" {
255+ p .Host = u .Ip
256+ }
257+ if _ , sport , err := net .SplitHostPort (p .Host ); err == nil && sport != "" {
258+ return nil , fmt .Errorf ("host or ip must not contain port" )
259+ }
260+ p .Port = int (u .Port )
261+ return p , nil
262+ }
263+
264+ func buildRequestURI (u * livekit.SIPRequestDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
265+ if u == nil {
266+ return buildLegacyURI (legacyUser , legacyAddr , tr )
267+ }
268+ switch u := u .Uri .(type ) {
269+ default :
270+ case * livekit.SIPRequestDest_Raw :
271+ return buildRawURI (u .Raw , tr )
272+ case * livekit.SIPRequestDest_Values :
273+ return buildValuesURI (u .Values , tr )
274+ }
275+ return nil , fmt .Errorf ("invalid request URI type" )
276+ }
277+
278+ func buildFromToURI (u * livekit.SIPNamedDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.Uri , error ) {
279+ if u == nil {
280+ return buildLegacyURI (legacyUser , legacyAddr , tr )
281+ }
282+ switch u := u .Uri .(type ) {
283+ default :
284+ case * livekit.SIPNamedDest_Raw :
285+ return buildRawURI (u .Raw , tr )
286+ case * livekit.SIPNamedDest_Values :
287+ return buildValuesURI (u .Values , tr )
288+ }
289+ return nil , fmt .Errorf ("invalid URI type" )
290+ }
291+
292+ func buildFromHeader (u * livekit.SIPNamedDest , legacyName * string , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.FromHeader , error ) {
293+ su , err := buildFromToURI (u , legacyUser , legacyAddr , tr )
294+ if err != nil {
295+ return nil , err
296+ }
297+ h := & sip.FromHeader {
298+ Address : * su ,
299+ }
300+ if u != nil {
301+ h .DisplayName = u .DisplayName
302+ } else if legacyName != nil {
303+ h .DisplayName = * legacyName
304+ } else {
305+ // Nothing specified, preserve legacy behavior
306+ h .DisplayName = su .User
307+ }
308+ return h , nil
309+ }
310+
311+ func buildToHeader (u * livekit.SIPNamedDest , legacyUser , legacyAddr string , tr livekit.SIPTransport ) (* sip.ToHeader , error ) {
312+ if u != nil && legacyUser != "" {
313+ return nil , errors .New ("cannot use both CallTo and SipToHeader" )
314+ }
315+ su , err := buildFromToURI (u , legacyUser , legacyAddr , tr )
316+ if err != nil {
317+ return nil , err
318+ }
319+ h := & sip.ToHeader {
320+ Address : * su ,
321+ }
322+ if u != nil {
323+ h .DisplayName = u .DisplayName
324+ }
325+ return h , nil
326+ }
327+
328+ func buildOutboundHeaders (req * rpc.InternalCreateSIPParticipantRequest , defaultHost string ) (* sip.Uri , * sip.FromHeader , * sip.ToHeader , error ) {
329+ uri , err := buildRequestURI (req .SipRequestUri , req .CallTo , req .Address , req .Transport )
330+ if err != nil {
331+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid request URI: %w" , err ))
332+ }
333+ to , err := buildToHeader (req .SipToHeader , req .CallTo , req .Address , req .Transport )
334+ if err != nil {
335+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid To header: %w" , err ))
336+ }
337+ fromHost := req .Hostname
338+ if fromHost == "" {
339+ fromHost = defaultHost
340+ }
341+ from , err := buildFromHeader (req .SipFromHeader , req .DisplayName , req .Number , fromHost , req .Transport )
342+ if err != nil {
343+ return nil , nil , nil , psrpc .NewError (psrpc .InvalidArgument , fmt .Errorf ("invalid From header: %w" , err ))
344+ }
345+ return uri , from , to , nil
346+ }
347+
178348func (c * Client ) createSIPParticipant (ctx context.Context , req * rpc.InternalCreateSIPParticipantRequest ) (resp * rpc.InternalCreateSIPParticipantResponse , retErr error ) {
179349 if c .mon .Health () != stats .HealthOK {
180350 return nil , siperrors .ErrUnavailable
181351 }
182352 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 == "" {
353+ if req .RoomName == "" {
190354 return nil , psrpc .NewErrorf (psrpc .InvalidArgument , "room name must be set" )
191355 }
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- }
356+ defaultHost := c .ContactURI (TransportFrom (req .Transport )).GetHost ()
204357 log := c .log
205358 if req .ProjectId != "" {
206359 log = log .WithValues ("projectID" , req .ProjectId )
@@ -212,22 +365,28 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
212365 if err != nil {
213366 return nil , err
214367 }
368+ uri , from , to , err := buildOutboundHeaders (req , defaultHost )
369+ if err != nil {
370+ return nil , err
371+ }
215372 tid := traceid .FromGUID (req .SipCallId )
216373 log = log .WithValues (
217374 "callID" , req .SipCallId ,
218375 "traceID" , tid .String (),
219376 "room" , req .RoomName ,
220377 "participant" , req .ParticipantIdentity ,
221378 "participantName" , req .ParticipantName ,
222- "fromHost" , req .Hostname ,
223- "fromUser" , req .Number ,
224- "toHost" , req .Address ,
225- "toUser" , req .CallTo ,
379+ "fromHost" , from .Address .Host ,
380+ "fromUser" , from .Address .User ,
381+ "toHost" , to .Address .Host ,
382+ "toUser" , to .Address .User ,
383+ "reqHost" , uri .Host ,
384+ "reqUser" , uri .User ,
226385 "direction" , "outbound" ,
227386 )
228387
229388 req .ParticipantAttributes = maps .Clone (req .ParticipantAttributes ) // shallow clone - string/string map. Needed to avoid mutating psrpc req
230- initial := c .createSIPCallInfo (req )
389+ initial := c .createSIPCallInfo (uri , from , to , req )
231390 state := NewCallState (c .getStateHandler (req .ProjectId , req .Observability , initial ), initial )
232391
233392 defer func () {
@@ -255,11 +414,10 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
255414 },
256415 }
257416 sipConf := sipOutboundConfig {
258- address : req .Address ,
259417 transport : req .Transport ,
260- host : req . Hostname ,
261- from : req . Number ,
262- to : req . CallTo ,
418+ uri : uri ,
419+ from : from ,
420+ to : to ,
263421 user : req .Username ,
264422 pass : req .Password ,
265423 dtmf : req .Dtmf ,
@@ -273,7 +431,6 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
273431 enabledFeatures : req .EnabledFeatures ,
274432 featureFlags : req .FeatureFlags ,
275433 mediaConfig : mconf ,
276- displayName : req .DisplayName ,
277434 }
278435 log .Infow ("Creating SIP participant" )
279436 call , err := c .newCall (ctx , tid , c .conf , log , LocalTag (req .SipCallId ), roomConf , sipConf , state , req .ProjectId )
@@ -299,13 +456,10 @@ func (c *Client) createSIPParticipant(ctx context.Context, req *rpc.InternalCrea
299456 return info , nil
300457}
301458
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- }
459+ func (c * Client ) createSIPCallInfo (uri * sip.Uri , from * sip.FromHeader , to * sip.ToHeader , req * rpc.InternalCreateSIPParticipantRequest ) * livekit.SIPCallInfo {
460+ toUri := ConvertURI (& to .Address )
461+ fromUri := ConvertURI (& from .Address )
462+ fromUri .Addr = netip .AddrPortFrom (c .sconf .SignalingIP , uint16 (c .conf .SIPPort ))
309463
310464 callInfo := & livekit.SIPCallInfo {
311465 CallId : req .SipCallId ,
@@ -316,7 +470,7 @@ func (c *Client) createSIPCallInfo(req *rpc.InternalCreateSIPParticipantRequest)
316470 ParticipantAttributes : req .ParticipantAttributes ,
317471 CallDirection : livekit .SIPCallDirection_SCD_OUTBOUND ,
318472 ToUri : toUri .ToSIPUri (),
319- FromUri : fromiUri .ToSIPUri (),
473+ FromUri : fromUri .ToSIPUri (),
320474 CreatedAtNs : time .Now ().UnixNano (),
321475 MediaEncryption : req .MediaEncryption .String (),
322476 EnabledFeatures : req .EnabledFeatures ,
0 commit comments