discover stun server

This commit is contained in:
Daniel Gultsch 2020-04-08 17:52:47 +02:00
parent 859bc0bef3
commit ca9b95fc9c
4 changed files with 103 additions and 48 deletions

View file

@ -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";

View file

@ -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);
}
}
}

View file

@ -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<PeerConnection.IceServer> 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<PeerConnection.IceServer> listBuilder = new ImmutableList.Builder<>();
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element services = response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
final List<Element> 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<PeerConnection.IceServer> 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<PeerConnection.IceServer> iceServers);
}
}

View file

@ -132,7 +132,7 @@ public class WebRTCWrapper {
);
}
public void initializePeerConnection() {
public void initializePeerConnection(final List<PeerConnection.IceServer> iceServers) {
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
CameraVideoCapturer capturer = null;
@ -193,10 +193,6 @@ public class WebRTCWrapper {
this.localVideoTrack = videoTrack;
final List<PeerConnection.IceServer> 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");