From ca9b95fc9c6e20eedf09c05ca1351507ff56ff1d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Apr 2020 17:52:47 +0200 Subject: [PATCH] discover stun server --- .../eu/siacs/conversations/xml/Namespace.java | 1 + .../conversations/xmpp/XmppConnection.java | 4 + .../xmpp/jingle/JingleRtpConnection.java | 140 ++++++++++++------ .../xmpp/jingle/WebRTCWrapper.java | 6 +- 4 files changed, 103 insertions(+), 48 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index a53362168..31b3420dd 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.xml; public final class Namespace { public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; + public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2"; public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7c9374bc8..61a156f69 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1902,5 +1902,9 @@ public class XmppConnection implements Runnable { public boolean bookmarks2() { return Config.USE_BOOKMARKS2 /* || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT)*/; } + + public boolean extendedServiceDiscovery() { + return hasDiscoFeature(Jid.of(account.getServer()),Namespace.EXTERNAL_SERVICE_DISCOVERY); + } } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 85008ef56..da9e99908 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -16,12 +16,15 @@ import java.util.List; import java.util.Map; import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import rocks.xmpp.addr.Jid; @@ -51,6 +54,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web super(jingleConnectionManager, id, initiator); } + private static State reasonToState(Reason reason) { + switch (reason) { + case SUCCESS: + return State.TERMINATED_SUCCESS; + case DECLINE: + case BUSY: + return State.TERMINATED_DECLINED_OR_BUSY; + case CANCEL: + case TIMEOUT: + return State.TERMINATED_CANCEL_OR_TIMEOUT; + default: + return State.TERMINATED_CONNECTIVITY_ERROR; + } + } + @Override void deliverPacket(final JinglePacket jinglePacket) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": packet delivered to JingleRtpConnection"); @@ -85,21 +103,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web jingleConnectionManager.finishConnection(this); } - private static State reasonToState(Reason reason) { - switch (reason) { - case SUCCESS: - return State.TERMINATED_SUCCESS; - case DECLINE: - case BUSY: - return State.TERMINATED_DECLINED_OR_BUSY; - case CANCEL: - case TIMEOUT: - return State.TERMINATED_CANCEL_OR_TIMEOUT; - default: - return State.TERMINATED_CONNECTIVITY_ERROR; - } - } - private void receiveTransportInfo(final JinglePacket jinglePacket) { if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) { final RtpContentMap contentMap; @@ -211,22 +214,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (rtpContentMap == null) { throw new IllegalStateException("initiator RTP Content Map has not been set"); } - setupWebRTC(); - final org.webrtc.SessionDescription offer = new org.webrtc.SessionDescription( - org.webrtc.SessionDescription.Type.OFFER, - SessionDescription.of(rtpContentMap).toString() - ); - try { - this.webRTCWrapper.setRemoteDescription(offer).get(); - org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get(); - final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); - final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); - sendSessionAccept(respondingRtpContentMap); - this.webRTCWrapper.setLocalDescription(webRTCSessionDescription); - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to send session accept", e); + discoverIceServers(iceServers -> { + setupWebRTC(iceServers); + final org.webrtc.SessionDescription offer = new org.webrtc.SessionDescription( + org.webrtc.SessionDescription.Type.OFFER, + SessionDescription.of(rtpContentMap).toString() + ); + try { + this.webRTCWrapper.setRemoteDescription(offer).get(); + org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get(); + final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); + final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); + sendSessionAccept(respondingRtpContentMap); + this.webRTCWrapper.setLocalDescription(webRTCSessionDescription); + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to send session accept", e); - } + } + }); } private void sendSessionAccept(final RtpContentMap rtpContentMap) { @@ -346,17 +351,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void sendSessionInitiate(final State targetState) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate"); - setupWebRTC(); - try { - org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get(); - final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); - Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description); - final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); - sendSessionInitiate(rtpContentMap, targetState); - this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e); - } + discoverIceServers(iceServers -> { + setupWebRTC(iceServers); + try { + org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get(); + final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); + Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description); + final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); + sendSessionInitiate(rtpContentMap, targetState); + this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e); + } + }); } private void sendSessionInitiate(RtpContentMap rtpContentMap, final State targetState) { @@ -481,9 +488,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private void setupWebRTC() { + private void setupWebRTC(final List iceServers) { this.webRTCWrapper.setup(this.xmppConnectionService); - this.webRTCWrapper.initializePeerConnection(); + this.webRTCWrapper.initializePeerConnection(iceServers); } private void acceptCallFromProposed() { @@ -559,4 +566,51 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void updateEndUserState() { xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState()); } + + private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) { + if (id.account.getXmppConnection().getFeatures().extendedServiceDiscovery()) { + final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(Jid.of(id.account.getJid().getDomain())); + request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); + xmppConnectionService.sendIqPacket(id.account, request, (account, response) -> { + ImmutableList.Builder listBuilder = new ImmutableList.Builder<>(); + if (response.getType() == IqPacket.TYPE.RESULT) { + final Element services = response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); + final List children = services == null ? Collections.emptyList() : services.getChildren(); + for (final Element child : children) { + if ("service".equals(child.getName())) { + final String type = child.getAttribute("type"); + final String host = child.getAttribute("host"); + final String port = child.getAttribute("port"); + final String transport = child.getAttribute("transport"); + final String username = child.getAttribute("username"); + final String password = child.getAttribute("password"); + if (Arrays.asList("stun", "type").contains(type) && host != null && port != null && "udp".equals(transport)) { + PeerConnection.IceServer.Builder iceServerBuilder = PeerConnection.IceServer.builder(String.format("%s:%s:%s", type, host, port)); + if (username != null && password != null) { + iceServerBuilder.setUsername(username); + iceServerBuilder.setPassword(password); + } + final PeerConnection.IceServer iceServer = iceServerBuilder.createIceServer(); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": discovered ICE Server: " + iceServer); + listBuilder.add(iceServer); + } + } + } + } + List iceServers = listBuilder.build(); + if (iceServers.size() == 0) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no ICE server found " + response); + } + onIceServersDiscovered.onIceServersDiscovered(iceServers); + }); + } else { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": has no external service discovery"); + onIceServersDiscovered.onIceServersDiscovered(Collections.emptyList()); + } + } + + private interface OnIceServersDiscovered { + void onIceServersDiscovered(List iceServers); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 397ddbbcd..1688a8ecb 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -132,7 +132,7 @@ public class WebRTCWrapper { ); } - public void initializePeerConnection() { + public void initializePeerConnection(final List iceServers) { PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory(); CameraVideoCapturer capturer = null; @@ -193,10 +193,6 @@ public class WebRTCWrapper { this.localVideoTrack = videoTrack; - - final List iceServers = ImmutableList.of( - PeerConnection.IceServer.builder("stun:xmpp.conversations.im:3478").createIceServer() - ); final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver); if (peerConnection == null) { throw new IllegalStateException("Unable to create PeerConnection");