ICE, ou Interactive Connectivity Establishment, apporte un niveau d’abstraction aux protocoles STUN et TURN décrits dans les articles précédents (respectivement ici et là). Alors que beaucoup de solutions, et STUN et TURN en font partie lorsqu’ils sont utilisés indépendamment, plaçaient l’intelligence au niveau des serveurs (ceux-ci détenant le maximum d’informations), ICE fait le pari inverse : intégrer cette intelligence au niveau du client. En effet, comme le discutera cet article ainsi que le suivant, les serveurs ne servent désormais que d’outils permettant au client d’apprendre et de connaître son environnement.
ICE est un protocole complexe. Il n’apporte en effet pas simplement une couche d’abstraction aux protocoles STUN/TURN, mais propose aussi des mécanismes de gestion très poussés, lui permettant notamment de lister, puis de sélectionner le meilleur lien entre deux points. Ce premier article présente les échanges initiaux dans une négociation initiaux, et un prochain article prendra en considération la sélection du meilleur lien.
Avant de poursuivre cet article, il est essentiel de note plusieurs points importants avant de considérer l’usage de ICE :
- Pour que ses mécanismes fonctionnent, il est nécessaire que les deux extrémités supportent ICE.
- Des serveurs STUN et/ou TURN doivent être disponibles.
- Dans son déploiement le plus strict, ICE est totalement automatique, nécessitant l’usage de DNS configurés correctement (enregistrements SRV _stun et _turn notamment).
Globalement, les échanges initiaux de ICE prennent la forme ci-dessus. Le client initie le processus en récupérant l’ensemble de ses couples adresse/port (nommés « candidats » au sein de ICE) locaux. Ces candidats représente toutes les interfaces, physiques ou virtuelles (VPN par exemple), configurées au niveau du système d’exploitation de l’équipement. Un port est ensuite, pour chacune de ces interfaces, choisi pour chaque flux dont la future connexion sera composée (aussi appelé « composants ». Par exemple, si une communication audio et vidéo souhaite être établie, le client réservera au minimum deux ports par interfaces. Puisque ces flux ne sont généralement pas considérés sans un protocole de contrôle (RTCP dans le cadre de RTP, par exemple), il faut par la même occasion leur réserver un port, soit un total de 4 ports par interface.
Lorsque ces candidats sont listés, il va récupérer ensuite un candidat dit réflexif (reflexive candidate, et plus spécifiquement server reflexive, distinction qui sera utile par la suite), à partir des candidats locaux. Ces candidats réflexifs sont récupérés à partir des serveurs STUN. Dans le même ordre d’idée, et si un serveur TURN est utilisé, une adresse relayée est aussi récupérée (relayed candidate), ce qui peut s’avérer utile selon l’environnement considéré. Nous discuterons ce point plus en détail dans notre prochain article, mais ICE est capable de choisir le meilleur lien entre deux hôtes. De ce fait, l’IETF recommande l’usage de serveurs TURN qui fournissent des informations plus exhaustives (adresse réflexive et relayée) qu’un simple serveur STUN.
Une fois ces candidats récupérés, le client leur attribue une fondation correspondant à un identifiant unique pour un session donnée. Plusieurs candidats peuvent avoir une fondation identique s’ils sont de même type ou ont la même base, ont été obtenus à l’aide du même protocole de transport (TCP, UDP) et, dans le cas des candidats reflexive ou relayed, ces derniers doivent provenir du même serveur. Une fondation identique signifie généralement que un de ces candidats pourra être supprimé au cours du processus ICE. En l’attente de ce moment toutefois, le client se doit de maintenir ces candidats en vie, en rafraîchissant par exemple les associations au niveau du NAT.
Lorsqu’un client récupère tous ses candidats, il les ordonne en leur assignant des priorités. La priorité de chaque candidat dépend de plusieurs critères :
- type_pf – La préférence selon le type de candidat (de 0 à 126). Par exemple, un candidat local étant toujours préféré, il lui sera assignée la meilleure priorité (126) alors que le candidat le moins performant (relayé) récupèrera la moins bonne priorité (0).
- local_pf – La préférence de l’interface (de 0 à 65 535).
- comp_id – L’identifiant du composant (flux).
pr = (224 × type_pf) + (28 × local_pf) + (20 × (256 – comp_id))
Lorsque chaque candidat a reçu une priorité en fonction de ces paramètres, les candidats redondants sont éliminés. Un candidat est redondant par exemple dans le cas où un équipement dispose d’un accès direct à Internet, sans devoir traverser de NAT. Dans ce cas en effet, les candidat local et réflexif seront strictement identiques. Il est intéressant de noter que les candidats locaux auront une meilleure priorité que les candidats réflexifs ou encore relayés. Par conséquent, dans certains cas, et bien que les candidats locaux n’aboutissent en règle générale jamais, il est parfaitement possible que cela puisse fonctionner dans certains cas spécifiques, comme suggéré dans la figure ci-dessous, dans laquelle ICE choisira de passer par l’interface WiFi au lieu de sortir par le NAT !
Nous avons mentionné au début de cet article que ICE ne fonctionne correctement que si la partie distante supporte aussi ICE. Dans l’optique d’être rétro-compatible avec des implémentations ne disposant pas de cette fonctionnalité, le protocole définit l’usage de candidats par défaut qui correspondent au candidat qui aurait été habituellement utilisé par le client dans une transaction commune. Ce candidat est généralement de type server reflexive, et est placé par exemple dans les attributs c= (pour l’adresse) et m= (pour le port) du corps SDP.
Lorsque l’ensemble de ces étapes est accompli, le client à l’origine de la requête envoie ses candidats à la partie distante.
v=0
o=jdoe 2890844526 2890842807 IN IP4 10.0.1.1
s=
c=IN IP4 192.0.2.3
t=0 0
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=ice-ufrag:8hhY
m=audio 45664 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=candidate:1 1 UDP 2130706178 10.0.1.1 8998 typ local
a=candidate:2 1 UDP 1694498562 192.0.2.3 45664 typ srflx raddr 10.0.1.1 rport 8998
Dans cet exemple de corps SDP envoyé par exemple au travers d’un message SIP (INVITE), nous retrouvons plusieurs éléments dont nous avons discuté précédemment. Premièrement, nous pouvons observer que le contenu des attributs c= et m= se retrouvent dans un des candidats proposés, ce qui est essentiel dans le cadre d’une transmission formée de manière adéquate. La destination, si elle supporte ICE, s’attend à retrouver un candidat correspondant aux informations par défaut. Il est intéressant de remarquer aussi certains attributs spécifiques à ICE, comme par exemple ice-pwd et ice-ufrag qui représentent le mot de passe et nom d’utilisateur qui seront utilisés par la suite dans le cadre des tests de connectivités (voir notre prochain article pour plus d’informations à ce sujet).
Dans le cas où la destination ne supporte pas ICE, les mécanismes classiques sont utilisés pour négocier la communication. Toutefois, si le destinataire supporte le protocole, il va à son tour effectuer exactement le même procédé qui vient d’être décrit dans ces lignes. De ce fait, lorsque ce processus est achevé, chaque partie de la communication dispose de tous les candidats, tant locaux que distants, qui pourront être utilisés par la suite. Lors de ce processus, un rôle est défini pour chaque hôte : ICE-CONTROLLING (contrôleur) est généralement donné à la partie initiant le dialogue, alors que ICE-CONTROLLED (contrôlé) est associé à la partie distante. Le contrôleur prendra à sa charge la majeure partie des décisions, le contrôlé participant seulement aux différents échanges amenant à ce choix.
Ces échanges seront abordé dans notre prochain article qui conclura cette discussion sur le protocole ICE en traitant de l’approche choisie par l’IETF pour vérifier et élire un candidat au lieu d’un autre.