Outernet

« Reverse engineering » Outernet : modulation, codage et cadrage

Traduction d’une publication de Daniel Estévez EA4GPZ / M0HXM (destevez) postée le October 15, 2016

Outernet est une entreprise dont le but est de faciliter l’accès mondial au contenu Internet. Ils visent à fournir une liaison descendante de contenu Internet sélectionné via des satellites géostationnaires. Actuellement, ils fournissent des flux de données provenant de trois satellites Inmarsat sur la bande L (environ 1,5 GHz environ). Cela leur donne une couverture presque mondiale. Le débit binaire en liaison descendante est d’environ 2 kbps ou 20 Mo de contenu par jour.

La liaison descendante est utilisée pour diffuser des fichiers, principalement de contenu éducatif ou informatif, et récemment, elle diffuse également certaines données APRS . Comme il s’agit d’une nouvelle technologie radio avec laquelle jouer, elle commence à attirer l’attention de certains opérateurs de radio amateur et d’autres personnes technophiles.

La plupart des logiciels Outernet sont open-source, à l’exception de certaines parties clés du récepteur, qui sont de source fermée et distribuées uniquement sous forme de fichiers binaires gratuits. Les détails du format du signal ne sont pas connus du public, donc la seule façon de recevoir le contenu est d’utiliser les binaires à source fermée Outernet. Pourquoi Outernet a décidé de faire cela m’échappe. Je trouve que cela est contraire aux principes de diffusion de contenu Internet. Les spécifications du protocole doivent être publiques. De plus, en tant qu’opérateur de radio amateur, je trouve qu’il n’est pas acceptable de travailler avec un récepteur de boîte noire dont je ne peux pas savoir quel type de signal reçoit et comment il le fait. En effet, l’esprit Radio Amateur est assez lié à certains égards à la philosophie du mouvement du Logiciel Libre.

Pour cette raison, j’ai décidé de rétroconcevoir le signal et le protocole Outernet dans le but de publier les détails et de construire un récepteur open-source. Au cours des derniers jours, j’ai réussi à rétroconcevoir toutes les spécifications de la modulation, du codage et du cadrage. J’ai publié toutes les mises à jour de développement sur mon compte Twitter . J’ai construit un récepteur GNUradio Outernet qui est capable d’obtenir des trames Outernet à partir du signal de bande L. Les protocoles utilisés dans ces trames sont encore inconnus, il y a donc encore beaucoup de travail d’ingénierie inverse à faire.

Les deux seuls éléments de source fermée du logiciel Outernet sont appelés sdr100et ondd. sdr100est le récepteur. Il utilise un dongle SDR pour recevoir le signal en bande L et décoder les trames Outernet. Ensuite, il passe les cadres Outernet à ondd, qui est le démon chargé de faire quelque chose d’utile avec les cadres. Son travail principal consiste à reconstruire et décompresser les fichiers en cours de streaming.

En particulier, sdr100est un peu polémique, car il me semble qu’il viole la licence GPL, car il relie librltsdret libmirisdrqui sont des logiciels GPL (pas LGPL). J’ai essayé d’écrire aux développeurs d’Outernet, mais ils ne semblent pas s’en soucier.

Dans tous les cas, mon récepteur GNUradio Outernet est maintenant capable de remplacer sdr100et d’envoyer des trames Outernet à ondd. Je commence à le faire pour inverser l’ingénierie des protocoles utilisés dans les cadres, car l’objectif est également de remplacer ondd(ou au moins de proposer un logiciel open source qui fait quelque chose d’utile avec les cadres Outernet).

Dans mon effort d’ingénierie inverse, l’aide de Scott Chapman K4KDR et Balint Seeber a été inestimable. Scott fait pour moi des enregistrements SDR du signal Outernet, car je n’ai pas de récepteur Outernet. Le travail de Balint a été très inspirant pour moi, en particulier ses diapositives sur l’analyse des signaux aveugles et son bloc Auto FEC GNUradio de gr-baz .

La première chose que nous notons lors de l’ingénierie inverse du signal est qu’il est un peu plus de 4 kHz de large. Nous savons que c’est probablement un signal PSK quelconque, mais BPSK et QPSK sont tous deux de bons candidats. Pour voir de quel type de PSK il s’agit, nous étudions les puissances du signal. Nous élevons le signal PSK en bande de base complexe à la puissance 2 et observons s’il y a une grande composante continue dans le signal que nous obtenons. Dans ce cas, il y en a, donc le signal est BPSK. S’il n’y en avait pas, nous élèverions le signal à la puissance 4, où un composant DC indiquerait QPSK, et ainsi de suite afin de détecter un PSK d’ordre supérieur. Cette astuce fonctionne en raison de la symétrie des constellations PSK. Les symboles BPSK sont distants de 180 ° (c’est-à-dire qu’ils sont opposés) tandis que les symboles QPSK sont séparés de 90 ° (c’est-à-dire qu’ils sont liés par les 4 racines de l’unité d’ordre 4). Donc, en élevant à la puissance 2 les symboles BPSK ils deviennent le même symbole, donc le signal résultant a DC. De même, lors de l’élévation à la puissance 4 d’un signal QPSK, les quatre symboles s’effondrent.

Ensuite, nous utilisons l’analyse cyclostationnaire pour déduire le débit de symboles. Nous multiplions le signal par le conjugué complexe du signal retardé d’un échantillon. Le signal résultant aura une forte composante continue. La prochaine composante de fréquence forte sera à une fréquence égale au débit de symboles. Cela fonctionne parce que le signal et le signal retardé sont plus ou moins égaux la plupart du temps, car les deux échantillons sont toujours dans le même symbole, donc nous obtenons 1 la plupart du temps. Cependant, lorsque le signal passe simplement d’un symbole à un autre symbole, le signal et le signal retardé seront très différents et nous obtenons quelque chose qui n’est pas 1. Une sorte de pic. Ainsi, nous obtenons un pic chaque fois qu’un changement de symbole se produit, de sorte que le signal résultant a une forte composante de fréquence au taux de symbole.

Avec le recul, on savait déjà que le signal est de 4200 bauds BPSK, mais l’exécution de ce type de tests nous permet de confirmer que la modulation n’a pas changé récemment.

Puisque nous savons que le signal est de 4200 bauds BPSK mais que le flux de données n’est cité que pour environ 2 kbps par Outernet, nous soupçonnons qu’un F = 1/2 FEC est utilisé, probablement le r = 1/2, k = 7 convolutionnel habituel code avec des polynômes CCSDS. Étant donné que ce code admet certaines variations, le bloc Auto FEC de gr-baz est très utile, car il essaie de nombreuses combinaisons jusqu’à ce qu’un taux d’erreur binaire faible soit atteint, pour essayer de détecter automatiquement la variation utilisée. Ce bloc a besoin d’une version corrigée de GNUradio, car il est nécessaire de modifier le décodeur Viterbi pour qu’il produise des statistiques d’erreur sur les bits. Le patch donné par Balint est pour une ancienne version de GNUradio, j’ai donc dû le modifier. Voici un patch qui fonctionne avec GNUradio 3.7.10.1, au cas où quelqu’un en aurait besoin.

Le bloc Auto FEC ne fonctionne qu’avec QPSK. Je l’ai modifié pour le faire fonctionner avec BPSK (uniquement). La meilleure chose à faire serait probablement de le faire supporter plusieurs constellations PSK. Voici le patch BPSK pour Auto FEC.

Le bloc Auto FEC a trouvé que le code convolutif utilisé est le même que le bloc GNUradio «Decode CCSDS 27» mais avec l’ordre polynomial inversé (d’abord POLYB puis POLYA). Par conséquent, chaque paire de symboles logiciels doit être échangée avant le décodeur Viterbi. Comme avec tout signal BPSK codé avec un code convolutionnel r = 1/2, il y a l’ambiguïté de la façon de faire les paires dans le flux de symboles logiciels. Ainsi, nous exécutons une chaîne swap + Viterbi sur le flux de symboles logiciels et une autre chaîne sur le flux de symboles logiciels a retardé un symbole.

En utilisant le décodeur Viterbi patché, nous pouvons voir que notre décodeur Viterbi fonctionne en raison de la faible erreur de bit (ce qui correspond à une sortie positive et presque constante dans la sortie statistique du bloc «Decode CCSDS 27» modifié). Nous soupçonnons que nous devons utiliser un désembrouilleur après que le décodeur Viterbi et un tracé raster du flux binaire le confirme. Nous pouvons essayer les désembrouilleurs asynchrones populaires pour voir s’il y a de la chance et nous obtenons une certaine structure dans le tracé raster. Par exemple, le polynôme utilisé dans la radio par paquets G3RUH 9k6 est un bon premier choix. Dans ce cas, il n’y a pas eu de chance.

Étant donné que j’ai les fichiers binaires pour le récepteur à source fermée Outernet, il existe une autre façon d’attaquer ce problème: pour désosser le code assembleur. J’utilise les binaires du récepteur Linux x86_64 en bande L. Ce ne sont pas la dernière version, mais ils semblent fonctionner. La dernière version est uniquement disponible pour Linux sur ARM, car Outernet cible les ordinateurs à carte unique tels que le Raspberry Pi 3 et le CHIP à utiliser comme récepteur. Ils conseillent désormais d’exécuter leur logiciel ARM sur une machine virtuelle si l’on veut utiliser un ordinateur de bureau. J’ai démonté sdr100(c’est fait avec objdump -D) et j’ai rapidement trouvé que le brouilleur est implémenté dans une fonction appeléescrambler308, ce qui n’est pas très long. J’ai reconverti le code assembleur en C. C’est un processus lent qui nécessite de la concentration. Le code que j’ai obtenu était assez proche du code qui l’a finalement transformé en gr-outernet .

Comme vous pouvez le voir dans le code, le désembrouilleur a une sorte de compteur qui est parfois réinitialisé. Le compteur influence également parfois le bit de sortie. C’est quelque chose que je n’avais jamais vu auparavant, car je suis habitué aux brouilleurs multiplicatifs que j’ai décrits dans un post précédent . En utilisant « 308 » comme mot-clé pour ma recherche, j’ai découvert que ce brouilleur s’appelle brouilleur IESS-308. Il est décrit dans un document Intelsat qui n’est pas accessible au public. Cependant, j’ai réussi à trouver une description du brouilleur dans un autre document (voir page 28). Comme vous pouvez le voir, le diagramme de ce document correspond au code C obtenu à partir descrambler308, à l’exception du fait qu’il descrambler308inverse le bit de sortie.

À ce stade, nous devons nous soucier de la polarité du signal. Comme vous le savez peut-être, lors de la réception d’un signal BPSK, il y a une ambiguïté de phase de 180 ° qui se traduit par le fait que nous avons une ambiguïté sur la polarité du signal. Nous ne savons pas si nous recevons le train de bits d’origine ou une version inversée de celui-ci. Généralement, le codage différentiel est utilisé pour résoudre cette ambiguïté, mais lorsque vous utilisez un décodeur logiciel Viterbi, le décodage différentiel est effectué après le décodage Viterbi, simplement parce que le décodeur Viterbi fonctionne sur des symboles logiciels et que le décodeur différentiel ne peut pas fournir de symboles logiciels.

La question est maintenant de savoir si le décodage différentiel doit venir avant ou après le désembrouilleur ou s’il est utilisé du tout dans le signal Outernet (il existe d’autres façons de résoudre l’ambiguïté de polarité). En y réfléchissant, il est bon de savoir ce qui se passe avec les différents blocs de traitement si nous alimentons une version inversée du signal que nous attendons. Il est bien connu que pour un décodeur Viterbi avec les polynômes CCSDS habituels, nous obtenons juste une sortie inversée (à l’exception de quelques bits au début du flux). C’est simplement parce que les polynômes CCSDS ont un nombre impair de coefficients non nuls.

Le désembrouilleur IESS-308 a la même propriété, car la ligne de réinitialisation du compteur est obtenue en XORant un nombre pair de bits dans le flux, tandis que la sortie dépend d’un nombre impair de bits dans le flux. Ainsi, si nous raccordons le désembrouilleur directement après le décodeur de Viterbi, nous avons toujours une ambiguïté de polarité sur le signal.

Trouver le problème du codage différentiel était probablement la partie la plus difficile. C’était une question de tâtonnements aveugles. La plupart des essais produisent une sorte de structure perceptible sur le tracé raster de la sortie. Cela signifie que le désembrouilleur fonctionne et que nous sommes sur la bonne voie.

En examinant le code assembleur de, sdr100nous savons que HDLC est utilisé d’une manière ou d’une autre pour le cadrage, car plusieurs des noms des fonctions font référence à HDLC. Cependant, je n’ai pas réussi à obtenir de trames HDLC valides avec mon deframer de gr-kiss . J’ai également procédé à une ingénierie inverse des fonctions de somme de contrôle sdr100au cas où une autre somme de contrôle serait utilisée. Il s’est avéré être une implémentation basée sur une table de CRC16-CCITT, qui est la somme de contrôle spécifiée pour HDLC, et le bit-endianness a été géré correctement. Donc, rien d’inattendu ici.

La solution s’est avérée très simple: aucun codage différentiel n’est utilisé. Puisqu’il y a une ambiguïté sur la polarité du train binaire, un déframeur HDLC est exécuté à la fois sur le train binaire et sur le train binaire inversé. L’un de ces deux déframeurs obtiendra avec succès les images. Ainsi, il y a un total de 4 deframers HDLC, puisque nous avons également 2 décodeurs Viterbi. Avec cette configuration de décodeur, j’ai commencé à obtenir des trames HDLC correctes à partir du signal Outernet.

Pour résumer, les spécifications de modulation, de codage et de trame du signal en bande L Outernet sont les suivantes:

  • 4200baud BPSK
  • r = 1/2, k = 7 code convolutionnel avec polynômes CCSDS (polynômes échangés)
  • Brouilleur IESS-308
  • Pas de codage différentiel
  • Cadrage HDLC

Avec ces spécifications, le décodeur dans gr-outernet est capable d’obtenir des trames Outernet à partir du signal de bande L. Je ne connais toujours pas beaucoup les protocoles utilisés dans les cadres Outernet, car j’en ai besoin de beaucoup pour essayer de détecter certains modèles, rechercher le contenu en texte brut, etc. Cependant, j’ai déjà noté quelques éléments.

Voici un cadre Outernet typique:

pdu_length = 276 
contents = 
0000: ff ff ff ff ff ff 00 30 18 c1 dc a8 8f ff 01 04 
0010: 3c 02 00 00 18 00 01 00 00 00 08 11 10 e5 21 4b 
0020: 48 2c e0 77 00 86 4d 14 06 3c 24 f7 30 e7 19 4c 
0030: ed 60 d4 44 94 6a 4a 18 34 ad b2 b5 92 01 b7 87 
0040: 06 ba 80 61 a5 87 06 80 f6 04 12 f6 d9 12 13 02 
0050: 64 0b 68 94 21 36 01 ab af 01 50 d0 13 4b dc b6 
0060: 92 90 6b f4 76 27 73 3d 91 f5 84 3d 75 d9 77 90 
0070: d2 74 15 49 66 e5 9a 57 df df 72 28 32 48 97 ed 
0080 : 9a 46 6e 68 8e 72 b3 54 5f 52 ce f6 f5 de c1 fd 
0090: e4 e6 f8 a2 bd bb bb 65 cf 9e d0 ed 80 1e ad 8c 
00a0: 0c b8 59 28 41 cf 27 d3 cf a9 9e 28 06 8e c0 c8
00b0: 42 7a bd ea da ae 7e 41 ee 24 c2 f9 28 b7 35 f6
00c0: 8b 12 13 23 1f fb 0d 3e 32 49 b9 75 4b 31 d3 29 
00d0: 11 c1 48 a2 3b d4 8b 40 e6 2c 69 02 59 f2 f8 c8 
00e0: d2 ea aa ce 63 57 ed f7 25 42 8e 9b 21 d4 64 07 
00f0: 89 59 d0 47 d6 7b c7 3c c7 11 2c 91 d3 ca b1 52 
0100: ea ba be e3 00 39 fb be 6a 02 52 e3 8f ac ba 30 
0110: b7 d1 c2 3f

Je m’attends à ce que cela contienne un morceau d’un fichier compressé, car c’est la majeure partie du trafic Outernet. Presque toutes les trames ont une longueur de 276 octets. À une vitesse de près de 2100 bps (il faudrait tenir compte du bourrage de bits), une trame met environ 1,05 seconde à transmettre. C’est plutôt bien. Chaque seconde, vous obtenez un nouveau paquet si le signal est suffisamment bon pour que le décodeur Viterbi fasse son travail. Si le verrouillage du signal est momentanément perdu, vous ne perdez qu’une seconde de données.

Ce qui m’intrigue le plus dans les trames, c’est qu’elles ressemblent beaucoup aux trames Ethernet L2 . Le MAC de destination serait le MAC de diffusion ff:ff:ff:ff:ff:ff,
ce qui est logique, et le MAC de source serait 00:30:18:c1:dc:a8, qui se révèle être une adresse MAC universellement valide avec une OUI attribuée à Jetway Information Co., Ltd. Il existe une société appelée Jetway Computer ce qui rend les ordinateurs embarqués pour les applications industrielles, donc peut-être que cela a du sens. Cependant, l’éthertype serait0x8fff, qui n’est utilisé pour aucun protocole standard. Je pense que c’est probablement le cas: les trames sont de véritables trames Ethernet et le contenu est un protocole léger de type UDP qui chevauche juste au-dessus d’Ethernet (sans couche IP). Je n’ai pas trouvé de protocole standard qui fasse une telle chose et correspond à la structure de ces paquets. Outernet a probablement mis au point un protocole ad hoc simple. Je ne sais pas pourquoi quelqu’un gaspillerait 5% de la bande passante d’un signal à débit binaire déjà faible pour envoyer des en-têtes Ethernet (qui sont inutiles pour le récepteur), mais c’est amusant de voir des trames Ethernet être redirigées depuis l’orbite géostationnaire.

J’ai également joué avec l’injection des quelques images que j’ai (les enregistrements de Scott ne duraient qu’environ 1 minute) onddpour voir si cela pouvait être utile. onddécoute sur un SOCK_SEQPACKETsocket Unix /tmp/run/ondd.dataet sdr100écrit les trames qu’il décode dans ce socket. Malheureusement, socatn’a pas un bon support pour les SOCK_SEQPACKETsockets, mais il est facile d’écrire un petit programme qui écoute les trames du décodeur GNUradio par UDP et les écrit dans le onddsocket. Voici un tel programme. Je le publierai probablement mieux à l’avenir. straceest un excellent outil pour faire ce genre de recherche. C’est ce que j’ai utilisé pour découvrir le type de socket qui onddutilise et je l’utilise aussi pour garder un œil surondd pour voir si ça fait quelque chose.

En utilisant cette technique, j’ai réussi à repérer deux paquets spéciaux:

pdu_length = 60 
contents = 
0000: ff ff ff ff ff ff 00 30 18 c1 dc a8 8f ff 00 1c 
0010: 3c 00 00 00 81 00 00 18 01 04 6f 64 63 32 02 08 
0020: 00 00 00 00 57 f6 94 20 48 3a ca 8d 00 00 00 00 
0030: 00 00 00 00 00 00 00 00 00 00 00 00 

pdu_length = 60 
contents = 
0000: ff ff ff ff ff ff 00 30 18 c1 dc a8 8f ff 00 1c 
0010: 3c 00 00 00 81 00 00 18 01 04 6f 64 63 32 02 08 
0020: 00 00 00 00 57 fa a0 b0 11 56 ab ab 00 00 00 00 
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00

Comme vous pouvez le voir, ces paquets sont plus courts que les paquets normaux. Lorsqu’il onddreçoit l’un d’eux, il essaie de régler l’horloge système (bien sûr, il échoue, car je ne l’exécute pas en tant que root). L’horodatage qu’il utilise est 0x57f69420pour le premier paquet et 0x57faa0b0pour le deuxième paquet, qui sont en position 0x24à l’intérieur des paquets. De plus, ces horodatages correspondent à «Thu, 06 Oct 2016 18:12:48 GMT» et «Sun, 09 Oct 2016 19:55:28 GMT», qui correspondent bien à l’heure à laquelle les enregistrements de Scott ont été effectués.

Par conséquent, il est assez clair ce que sont ces paquets: ce ne sont que des paquets temporels qui sont utilisés pour régler l’horloge sur les récepteurs. C’est une très bonne idée, car les ordinateurs à carte unique utilisés comme récepteurs n’ont pas d’horloge en temps réel et bien sûr, l’utilisation de NTP n’est pas une option. Je m’attends à ce qu’un paquet de temps soit transmis toutes les minutes plus ou moins.

À l’heure actuelle, je ne sais pas quels sont les 4 autres octets non nuls qui suivent l’horodatage. Je ne sais pas non plus pourquoi il y a tant d’octets zéro. Le début du paquet est le même pour les deux, mais je m’attends à ce que ce soit une sorte d’en-tête qui identifie le service, en utilisant probablement un concept de ports.

Donc voilà, quelqu’un pourrait utiliser toutes ces informations pour construire une horloge Outernet entièrement fonctionnelle. J’espère qu’à l’avenir, nous saurons comment extraire des données plus utiles des cadres.