emiago/sipgo

Sip Dialog terminating mid call

Closed this issue · 6 comments

I have had success using the new dialogue server. I used the examples and added my own logic to properly respond to the SDP offer sent in the INVITE. The parts that are working are the dialogue starting when the call starts after the INVITE, and the dialogue properly stops when receiving BYE. My issue is if the call is longer than 30sec, the dialogue also just stops and kills the call. I have a hunch that this is probably due to a timeout somewhere, but I'm not sure what kind of keepalives are needed other than the code here. I've tried this with multiple sip phones and all have the same behaviour. I'm using UDP. Am I doing something wrong?

Here is an excerpt of the code I'm using:

func main() {
	ua, _ := sipgo.NewUA(sipgo.WithUserAgent("foobar"))
	uasContact := sip.ContactHeader{
		Address: sip.Uri{User: "foobar", Host: "192.168.22.122", Port: 5060},
	}
	cli, _ := sipgo.NewClient(ua)
	srv, _ := sipgo.NewServer(ua)
	dialogSrv := sipgo.NewDialogServer(cli, uasContact)

	srv.OnRegister(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = tx.Respond(sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil))
	})

	srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
		fmt.Printf("DIALOG STARTED!!!\n")
		dlg, err := dialogSrv.ReadInvite(req, tx)
		if err != nil {
			panic(err.Error())
		}
		sdpResp := generateSdpResponse(req.Body())
		_ = dlg.Respond(sip.StatusOK, "OK", sdpResp, sip.NewHeader("Content-Type", "application/sdp"))
		<-tx.Done()
		fmt.Printf("DIALOG DONE!!!\n")
	})

	srv.OnAck(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = dialogSrv.ReadAck(req, tx)
	})

	srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction) {
		_ = dialogSrv.ReadBye(req, tx)
	})

	ctx, _ := signal.NotifyContext(context.TODO(), os.Interrupt)
	go srv.ListenAndServe(ctx, "udp", "192.168.22.122:5060")
	<-ctx.Done()
}
emiago commented

@half2me thanks for reporting.
Yes I am trying to keep dialog interface for now very minimal. It does not provide all cases.

Timeout you are facing is actually just transaction timeout and should have nothing with dialog.
Consider that even if you do not do this

<-tx.Done()
fmt.Printf("DIALOG DONE!!!\n")

dialog will be managed and have proper ACK and BYE handled.

So this just indicates that transaction handler is done but not dialog. Dialog is done when BYE is received, or this line specifically.

_ = dialogSrv.ReadBye(req, tx)

After you send 200 OK, your call should be still handled and managed by dialog, but you normally do not want to terminate transaction too early (State handling transaction, resending) so therefore it is recomended to keep transaction open with

<-tx.Done()

and let either dialog terminates or timeout (64 * T1 = 30 seconds).

I also see some issue of misuse, or maybe I am missing some docs here. What could help maybe that dialog wraps
this call with own Done but this will just block even more.

What are some features that maybe I am still in research is that you have dialog monitoring. This could make sense for you use case, but maybe I am wrong?

dialogSrv.OnDialog(d Dialog) {
     switch d.State() {
        case "established":
        case "terminated""
     }
}

Basically I need to clean up some resources when a call ends. This could happen when I receive BYE, but this message could also never arrive, such is the nature of UDP. How will I know the call is finished?

emiago commented

@half2me Yes I see.
So you probably needs this dialog monitoring to be exposed.
What will allow you that outside of this transaction handlers, you deal only with dialog, but it will still be async.

This could happen when I receive BYE, but this message could also never arrive, such is the nature of UDP

True, this is now required some timeout handling + maybe keep alive over SIP.

So here I what I see that is missing

	srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
		dlg, err := dialogSrv.ReadInvite(req, tx)
		
		...
		_ = dlg.Respond(sip.StatusOK, "OK", sdpResp, sip.NewHeader("Content-Type", "application/sdp"))
		
		
		<-tx.Done()
		fmt.Printf("DIALOG DONE!!!\n")
	})
	
      dialogSrv.OnDialog(func(d DialogServerSession) {
                // Dialog created on 200 OK
                select {
                    case <-d.Done():
                            // Bye Received from UAC
                    case <-time.After(3 *time.Second):
                           d.Bye()
                }
                
                // Do some after dialog cleanup
      })
emiago commented

@half2me you can also checkout
https://github.com/emiago/sipgox/blob/c827447221daf40967212c605bc54db765a6d884/phone.go#L442

how this is handled. If you can ignore all other.

Basically I used seperate channel to draw out dialogServerSession and wrapped some done channel to signal when Bye is received. So there is some design missing currently

emiago commented

@half2me I have added Done() for dialog as well

Check main branch
So now you can

dlg, err := dialogSrv.ReadInvite(req, tx)
...
<-dlg.Done()
fmt.Printf("DIALOG DONE!!!\n")

Also I noticed sometimes cleanup is hard to achieve due to many actions to take.
So for now there is also exposed
dlg.Close()

Which will trigger dlg.Done()

Pls provide some feedback after checking this

emiago commented

For now dialog control should be enough with new calls. Clossing this issue