Search code examples
androidsipvoip-android

Android SIP call rings then gets ENETUNREACH (Network is unreachable)


I am trying to add outbound SIP calling into an Android app using android.net.sip.SipManager, I get a call to onRingBack but when the other side answers the call I get a call to onError with code -4 and message android.system.ErrnoException: sendto failed: ENETUNREACH (Network is unreachable).

I suspect that this is some kind of firewall or network issue because depending on the network I use, I get the ENETUNREACH error at different points in the process. With my VPN enabled, I get it before the onRingBack callback. With a public WiFi network, I never get the onError callback at all, I just get no callbacks after onRingBack. And with my personal WiFi network, I get the onError as described above. Yet somehow the LinPhoneAndroid app does not have this problem, so there must be a way to code around this.

For my test setup this, I created two accounts at linphone.org:

  • sip:[myphonenumber]@sip.linphone.org // iPhone
  • sip:[myusername]@sip.linphone.org // Android

I use the first account with the LinPhone iOS app on my iPhone 11. I use the second account with the LinPhone Android app on my Google Pixel 4a with Android 12. I verified I can successfully make calls from Android to iOS using these LinPhone apps.

I then coded my own app on Android to call the iPhone. It successfully registers with the SIP server with:

            sipProfile = SipProfile.Builder(username, domain).setPassword(password).build()
            val intent = Intent()
            intent.action = "android.SipDemo.INCOMING_CALL"
            val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, Intent.FILL_IN_DATA)
            sipManager.open(sipProfile, pendingIntent, null)
            sipManager.setRegistrationListener(sipProfile.uriString, object: SipRegistrationListener {
                override fun onRegistering(localProfileUri: String?) {
                    Log.d(TAG, "Registering")
                }

                override fun onRegistrationDone(localProfileUri: String?, expiryTime: Long) {
                    registrationExpiration = Date(expiryTime)
                    ...
                    Log.d(TAG, "Registration done.  LocalProfileUri: $localProfileUri Expiry Time: ${registrationExpiration}")
                }

                override fun onRegistrationFailed(
                    localProfileUri: String?,
                    errorCode: Int,
                    errorMessage: String?
                ) {
                    Log.d(TAG, "Registration failed.  LocalProfileUri: $localProfileUri,  Error code: $errorCode, Error MessagE: $errorMessage")
                }

            })

I can then initiate an outbound call to the iPhone with sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30), and the iPhone actually rings and can answer the call which launches the LinPhone iOS app and shows a call in progress timer. **When the iPhone starts ringing, my Android code gets a callback to onRingingBack. But when the iPhone answers the call it gets a call to onError with code -4 an d messagesendto failed: ENETUNREACH (Network is unreachable)

Here is the full code that initiates the call and implements the callbacks:

        audioCallListener = object: SipAudioCall.Listener() {
            public override fun onError(
                call: SipAudioCall?,
                errorCode: Int,
                errorMessage: String?
            ) {
                super.onError(call, errorCode, errorMessage)
                Log.d(TAG, "Error making call code: $errorCode, message: $errorMessage")

            }

            public override fun onReadyToCall(call: SipAudioCall?) {
                Log.d(TAG, "ready to call")
                super.onReadyToCall(call)
            }

            public override fun onRingingBack(call: SipAudioCall?) {
                Log.d(TAG, "onRingingBack")
                super.onRingingBack(call)
            }

            public override fun onCalling(call: SipAudioCall?) {
                Log.d(TAG, "onCalling")
                super.onCalling(call)
            }

            public override fun onCallHeld(call: SipAudioCall?) {
                Log.d(TAG, "onCallHeld")
                super.onCallHeld(call)
            }

            public override fun onCallBusy(call: SipAudioCall?) {
                Log.d(TAG, "onCallBusy")
                super.onCallBusy(call)
            }

            public override fun onChanged(call: SipAudioCall?) {
                val state = call?.state ?: -1
                Log.d(TAG, "onChanged state: ${state}")
                super.onChanged(call)
            }
            public override fun onRinging(call: SipAudioCall?, caller: SipProfile?) {
                Log.d(TAG, "Ringing...")
                super.onRinging(call, caller)
            }
            public override fun onCallEstablished(call: SipAudioCall) {
                mediaPlayer?.stop()
                call.startAudio()
                call.setSpeakerMode(true)
                call.toggleMute()
                Log.d(TAG, "Calls started")
                super.onCallEstablished(call)
            }

            public override fun onCallEnded(call: SipAudioCall) {
                Log.d(TAG, "Call ended")
                super.onCallEnded(call)
            }
        }
        GlobalScope.launch {
                Log.d(TAG, "Making call from ${sipProfile.getUriString()} to ${sipAddress}")
                try {
                    lastCall = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30);
                }
                catch (e: SipException) {
                    Log.e(TAG, "Call failed", e)
                }
        }

Solution

  • Unfortunately, the answer is to give up on using these APIs. Google has:

    This interface was deprecated in API level 31. 
SipManager and associated classes are no longer supported and should not be used as the basis of future VOIP apps.

    See here