Réouverture (progressive) de monrer.fr

Auteur :  x0r Publié le   Nombre de commentaires : 12
Mots-clefs : monrer transilien sncf gtfs train rer-web

Ça m'aura pris un peu plus longtemps que je le pensais, mais je vais enfin pouvoir rouvrir monrer.fr !

Comme cela faisait quand même presque 6 semaines de downtime, je pense que je vous dois quand même quelques explications.

Dans mon post précédent, j'avais expliqué comment j'avais fait de la rétroingénierie sur l'appli Transilien officielle pour Android (attention, vidéo en auto-play sur la page) afin de déterminer comment cette appli faisait pour récupérer les horaires en live des prochains trains de chaque gare. L'étape suivante était bien entendu d'implémenter le fruit de mes trouvailles. Et comme vous pouvez le constater, c'est plus ou moins pour cette raison que j'ai eu quelques ennuis.

Une alerte Nagios plus tard, je me suis rendu compte que le serveur qui héberge monrer.fr s'est soudainement fait bannir. Je ne pouvais donc plus récupérer les horaires temps réel des trains.

Après quelques échanges avec plusieurs employés de la SNCF qui ont pris l'initiative de me contacter (et que je salue au passage), je me suis mis en tête d'essayer l'API Open Data officielle que la SNCF fournit depuis quelques mois, et qui est (d'un point de vue fonctionnel) grosso modo équivalent aux sources de données que j'utilisais avant. Cette nouvelle source de données n'est pas encore tout à fait parfaite (en particulier, seules les gares des lignes C et L sont interrogeables), mais que l'ouverture du service à l'ensemble des gares Transilien serait pour bientôt. Cette restriction sur les gares interrogeables se reflète pour le moment dans monrer.fr, mais dès que j'aurai des nouvelles, je vous tiendrai au courant.

Les changements d'un point de vue technique

J'ai profité du downtime pour retaper une bonne partie du code qui s'occupait de la récupération des informations proprement dite, pour me permettre à l'avenir de confronter plus facilement plusieurs sources de données. Je le fais d'ailleurs déjà, car pour un train donné, je récupère son horaire temps réel via l'API et son horaire théorique à l'aide des horaires Transilien en GTFS pour calculer son retard.

J'ai aussi dû retaper la gestion des gares de manière générale, parce que toutes mes sources de données utilisent les codes UIC en interne alors que je m'étais longtemps fié aux codes TR3.

Sinon, le site sait désormais par quelles lignes sont desservies chaque gare. Ça se voit dans les pop-ups d'autocomplétion :

Nouvelle autocomplétion sur monrer.fr

Cette fonction paraît anodine mais cela me permettra au moins de poser les bases pour une feature request que j'ai eue, à savoir pouvoir filtrer les résultats par ligne.

Conclusion

J'ai attendu assez longtemps avant de rouvrir, parce que je voulais être sûr de ne pas laisser trop de bugs dans ce que j'allais faire. Cependant, tout n'est pas encore parfait, et il se peut que vous tombiez sur l'un des messages d'erreur suivants :

  • API is broken : erreur interne du serveur qui fournit l'API Prochains Départs. Suffit de rafraîchir la page et ça devrait repartir.

  • API call quota exceeded : là c'est parce que vous êtes trop nombreux. J'ai pour le moment toujours le quota à 10 appels par minute (ce qui me permet d'avoir 2 users regardant des gares différentes au maximum à un instant donné), mais heureusement pas pour longtemps.

Sinon, s'il y a autre chose, vous pouvez vous exprimer dans les commentaires, m'envoyer un mail ou jeter un œil au bugtracker du site.

Les fonctions variadiques en C

Auteur :  x0r Publié le   Nombre de commentaires : 4
Mots-clefs : programmation c stdarg variadique variadic fonction argument variable

Jusqu'à maintenant, tout programmeur débutant en C et ayant écrit ses propres fonctions sait qu'une fonction peut prendre un certain nombre de paramètres.

Chacune de ces fonctions ont un nombre fixe de paramètres : une fonction qui prend trois paramètres doit forcément être appelée en renseignant ces trois paramètres.

Cependant, il y a des fonctions qui semblent échapper à la règle et qui autorisent un nombre variable de paramètres. L'un d'entre eux est probablement la plus utilisée en C : il s'agit de printf. On peut donner autant d'arguments qu'on veut à printf, tant qu'on respecte les règles de syntaxe dans la chaîne de format (le premier argument à printf). Ces fonctions sont appelées fonctions variadiques pour cette raison.

On peut tout à fait écrire

printf("Bonjour le monde\n");

ou encore

printf("La réponse est %d", 42);

ou encore

printf("J'ai deux chats : %s et %s", "Mélusine", "Laptite");

Si printf le fait, alors pourquoi pas nous ? En fait, rien ne nous empêche d'écrire nos propres fonctions variadiques. Je vais donc vous montrer dans ce post comment on écrit de telles fonctions, comment on exploite un nombre variable d'arguments et quelles sont les précautions à prendre.

Fonctions variadiques, mode d'emploi

Le C est un langage qui a plus de 40 ans et la gestion des arguments variables a été standardisée dans le C89 (la fonction va_copy() a ensuite été ajoutée dans le C99). C'est pourquoi nous devons d'abord examiner les limitations de cette technique avant de commencer.

Limitations techniques

Tout d'abord, il faut savoir qu'une fonction variadique doit obligatoirement accepter au moins un argument fixe. On ne peut pas définir de fonction variadique qui puisse accepter zéro paramètre, c'est-à-dire que le code suivant ne fonctionnera pas :

void ma_fonction(...);

Il n'existe pas non plus de mécanisme qui permette de savoir si on a fini de lire la liste des arguments, ou qui permette de savoir combien d'arguments ont été passés. Il faut alors choisir l'une des deux solutions :

  • on donne un paramètre qui indique le nombre de paramètres qui suivent, un peu comme le paramètre argc de main() qui donne la taille du tableau argv ;
  • ou alors on exige de terminer la liste d'arguments par une valeur spéciale (une "sentinelle"), telle que NULL (possible uniquement lorsque la fonction prend comme argument des pointeurs).

L'une de ces deux solutions peut ne pas être possible à mettre en œuvre pour des raisons diverses et variées. D'ailleurs, dans le cas de printf, on part du principe qu'il y a autant d'arguments qui suivent la chaîne de format que de %s, %d ou autres dans cette chaîne, donc il peut y avoir d'autres solutions que les deux ci-dessus.

Enfin, bien que nous ne soyions pas obligés d'utiliser les mêmes types pour chacun des arguments d'une fonction variadique (comme pour printf), rien ne nous permet de savoir à l'avance de quels types sont les arguments, et rien ne nous permet d'imposer des contraintes sur leurs types. C'est pour cette raison que ce code compile même s'il segfaulte :

printf("%d %s", "bonjour", 42);

Comme beaucoup de choses en C, ce mécanisme fournit très peu de garde-fous et les occasions de se planter sont nombreuses si on ne fait pas attention.

Un coup d'œil au man

Pour utiliser des fonctions variadiques, il faudra inclure le fichier stdarg.h dans son fichier C.

La lecture de la page de man de stdarg(3) (je n'ai pas trouvé de versions en français à jour, désolé) montre que la gestion des arguments dans une fonction variadique se fait avec quatre fonctions ou macros :

  • va_start(), qui nous permet de commencer à parcourir la liste d'arguments passés à notre fonction ;
  • va_arg(), qui nous permet de récupérer un argument de cette liste à chaque appel ;
  • va_end(), qui permet de signaler qu'on a fini de lire la liste d'arguments ;
  • enfin, va_copy() si on a besoin de copier des listes d'arguments pour une raison ou une autre.

Si vous avez une machine UNIX sous la main, tapez la commande man 3 stdarg et vous aurez le manuel sous vos yeux.

Démonstration : la liste de courses

Pour illustrer le mécanisme des fonctions variadiques, je vous propose d'utiliser comme exemple une fonction qui se contente d'imprimer à l'écran une liste de courses (donc des chaînes). Un appel à cette fonction :

courses("oeufs", "chocolat", "sucre", "farine", NULL);

devra donner la sortie suivante :

J'ai 4 choses à acheter : oeufs, chocolat, sucre et farine.

L'algorithme est relativement simple et consiste en deux étapes :

  • Parcourir la liste d'arguments pour compter le nombre d'arguments passés à la fonction (le NULL de la fin ne compte pas) ;
  • Parcourir à nouveau la liste d'arguments depuis le début pour afficher la liste de courses.

Voici donc le code, commenté de manière appropriée :

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>


void courses(char* arg0, ...) 
{
    va_list ap;

    int i, nb_args;
    char* cur_arg = arg0;

    /* On n'est jamais trop prudent */
    if (arg0 == NULL) {
        printf("Je n'ai rien à acheter\n");
        return;
    }

    /* Commencer à parcourir la liste des paramètres.  La liste s'appelle ap
     * et le dernier argument fixe est arg0.  */
    va_start(ap, arg0);

    /* Ajouter 1 à nb_args jusqu'à ce qu'on lise un argument de type char*
     * égal à NULL.
     * Cette boucle compte le nombre d'arguments y compris le NULL, mais comme
     * on "oublie" de compter arg0, pas besoin de soustraire 1 du résultat 
     * final. */
    for (
        nb_args = 0; 
        cur_arg != NULL;
        nb_args++, cur_arg = va_arg(ap, char*)
    ); 

    /* On a fini (pour cette fois) */
    va_end(ap);

    printf("J'ai %d %s à acheter : ", nb_args, 
            (nb_args > 1) ? "choses" : "chose");

    /* Reparcourir à nouveau la liste de paramètres et les afficher un à un.
     * Remarquez que la boucle commence à 1 car le premier argument lu avec va_arg()
     * est en réalité le *deuxième* ingrédient de notre liste de courses. */
    printf("%s", arg0);
    va_start(ap, arg0);
    for (i = 1; i < nb_args; i++) {
        if (i == nb_args - 1)
            printf(" et ");
        else
            printf(", ");
        printf("%s", va_arg(ap, char*));
    }
    printf("\n");

    va_end(ap);
}



int main(int argc, char* argv[])
{
    courses(NULL);
    courses("oeufs", NULL);
    courses("oeufs", "chocolat", NULL);
    courses("oeufs", "chocolat", "sucre", "farine", NULL);
    return 0;
}

Comme on peut le voir, nous sommes déjà obligés de contourner quelques-unes des limitations techniques de stdarg.h pour faire une fonction aussi simple. La première, c'est qu'on est obligés d'avoir un argument "fixe", que j'ai appelé arg0, pour dénoter le premier élément de notre liste de courses. Les autres éléments sont récupérés en utilisant la fonction va_arg(ap, char*). Ici, tous nos arguments sont de type char*. La deuxième, c'est qu'on a été obligé de compter les arguments avant de pouvoir les afficher.

En tout cas, c'est tout ce qu'il faut pour que ça marche, et lorsqu'on exécute le programme, on voit apparaître à l'écran :

Je n'ai rien à acheter
J'ai 1 chose à acheter : oeufs
J'ai 2 choses à acheter : oeufs et chocolat
J'ai 4 choses à acheter : oeufs, chocolat, sucre et farine  

Conclusion

En résumé, pour utiliser les fonctions variadiques en C, la recette est la suivante :

  • inclure stdarg.h dans son programme ;
  • déclarer une variable de type va_list ;
  • l'initialiser avec va_start() ;
  • lire les arguments un par un avec va_arg() ;
  • terminer avec va_end().

Cet exemple est très simple mais je suis sûr qu'il y a d'autres usages utiles pour ce genre de fonctions. Personnellement, je m'en sers le plus souvent pour faire un wrapper autour de printf (ou une fonction qui se comporte que la même manière que printf) dans des situations où on n'a pas directement accès à stdout et stderr (lorsqu'on réalise une application SDL en plein écran par exemple).

Utiliser des types opaques en C

Auteur :  x0r Publié le   Nombre de commentaires : 2
Mots-clefs : programmation c code opaque type types struct malloc

Cela fait déjà quelques temps que je montre le langage C à ma chère et tendre Nausicaa et que je la vois faire de plus en plus de choses avec. Je me suis donc dit que je pourrais peut-être faire une série d'articles afin de l'aider à mieux coder, et les partager ici pour que vous en profitiez tous.

N'importe qui ayant fait du C de manière à peu près sérieuse a déjà utilisé des structs pour créer des types de données composites, par exemple des listes chaînées ou plus ou moins ce qu'on appellerait des classes dans des langages (orientés) objet.

Cependant, lorsqu'un programme C est constitué d'une multitude de modules et qu'il touche à de nombreux types de données, on rentre vite dans des problèmes de dépendances circulaires : un module a a besoin de la déclaration d'une struct type_b qui est dans le module b (le header b.h); or le module b a besoin de la déclaration d'un struct type_c dans le module c (fichier c.h), et ce module c a besoin de connaître l'existence du type struct type_a dans le module a (a.h). La compilation échoue alors sur une erreur de la forme « type struct type_a inconnu » alors qu'il est pourtant déclaré correctement.

Nous allons d'abord expliquer pourquoi ces dépendances circulaires n'en sont pas vraiment en expliquant la différence entre déclaration et définition ; nous verrons ensuite ce que sont les fameux types opaques et comment ceux-ci permettront de casser la majorité des dépendances circulaires.

La différence entre déclaration et définition

Imaginons que nous voulions mettre en place une structure de liste chaînée, très simple, et tant qu'on y est, générique. Alors, on va avoir deux fichiers, liste.h et liste.c. Un programmeur C novice écrirait alors quelque chose qui ressemblerait au listing suivant :

liste.h :

#ifndef HEADER_LISTE
#define HEADER_LISTE

typedef struct liste liste;

struct liste {
    void * data;
    liste* suivant;
};

liste* creation_liste();
liste* insertion(liste* l, void* data);

#endif

liste.c :

#include <stdlib.h>
#include "liste.h"

liste* creation_liste() {
    return NULL;
}

liste* insertion(liste* l, void* data) {
    liste* e;
    liste* new_element = malloc(sizeof(struct liste));
    new_element->data = data;
    new_element->suivant = NULL;

    if (l == NULL)
        return new_element;

    for (e = l; e->suivant != NULL; e = e->suivant);

    e->suivant = new_element;
    return l;
}

En C, il est très important de connaître la différence entre déclarer et définir un objet (que ce soit une fonction, une variable, un struct, enum, union ou typedef) :

  • Déclarer un objet, c'est signaler au compilateur qu'il existe. Par exemple, les prototypes des fonctions dans le .h sont des déclarations : on dit au compilateur « tiens, il y a une fonction creation_liste() et une fonction insertion() ; ce qu'ils font exactement est écrit ailleurs mais sache qu'ils existent vraiment » ;
  • Définir un objet, c'est expliciter au compilateur ce qu'est exactement cet objet. Si liste.h contient les déclarations de deux fonctions, on les définit dans le .c. Dans le cas de fonctions, on dit aussi qu'elles sont déclarées dans le .h et implémentées dans le .c.

La subtilité, c'est que lorsqu'on définit un objet qui n'a pas encore été déclaré ailleurs, on le déclare également. Par exemple, l'instruction

int a = 42;

et les deux instructions

int a;
a = 42;

font tous les deux une déclaration suivie d'une définition de la variable a.

Ajoutons maintenant un fichier main.c :

main.c :

#include <stdio.h>
#include "liste.h"

int main(int argc, char* argv[]) {
    liste* l = creation_liste();
    liste* e;

    int a = 42;
    int b = 1337;

    /* 
     * Normalement, ça c'est mal, parce que &a et &b ne sont plus
     * des pointeurs valables lorsqu'on sort de la fonction
     * main(), mais c'est juste pour la démonstration.
     */
    l = insertion(l, (void*)(&a));
    l = insertion(l, (void*)(&b));

    printf("Voici le contenu de ma liste : \n");
    for (e = l; e != NULL; e = e->suivant) {
        printf(" * %d\n", *(int*)(e->data));
    }

    return 0;
}

Ce fichier ajoute les entiers 42 et 1337 à une liste et les affiche aussitôt. Notre liste chaînée ne sait stocker que des pointeurs (les pointeurs void * sont des pointeurs génériques) ; il faut donc stocker les adresses de a et de b. Pour les afficher, on caste le void * en int * puis on déréférence le pointeur.

Passer aux types opaques

Ce genre de code fonctionne bien lorsqu'on a un petit projet. Cependant, il y a quelques problèmes potentiels :

  • si on décide de modifier la liste chaînée en liste doublement chaînée, ça casse les fonctions qui l'utilisent ;

  • si on décide de changer le nom des membres, ça casse toutes les fonctions qui les utilisent ;

  • chaque module utilisant liste.h peut casser les listes et peuvent par exemple modifier les pointeurs suivant alors que seules les fonctions dans liste.c ont besoin d'y toucher ;

  • enfin, chaque module utilisant notre liste chaînée a besoin de connaître la définition de la struct liste pour pouvoir fonctionner.

Le dernier point est le plus problématique, et c'est souvent la cause des dépendances circulaires. Heureusement, il est possible de séparer la déclaration et la définition de notre liste chaînée. Voilà comment on fait :

liste.h :

#ifndef HEADER_LISTE
#define HEADER_LISTE

typedef struct liste* liste_t;

liste_t creation_liste();
liste_t insertion(liste_t l, void* data);

#endif

La première modification majeure est qu'on a fait un typedef qui fait un alias vers un pointeur de struct liste. Ce typedef est parfaitement légal, car le compilateur connaît toujours la taille en mémoire d'un pointeur, indépendamment de ce vers quoi il pointe. Ainsi, un module qui inclut liste.h pour ensuite déclarer une liste_t maliste; quelque part dans une fonction ne fait pas d'erreurs à la compilation. De toute façon, nous ne faisions que manipuler des pointeurs vers ces structures tout au long.

On peut néanmoins se demander où est passée la définition de struct liste. Elle est dans le fichier .c correspondant :

liste.c :

#include <stdlib.h>
#include "liste.h"

struct liste {
    void * data;
    liste_t suivant;
};

liste_t creation_liste() {
    return NULL;
}

liste_t insertion(liste_t l, void* data) {
    liste* e;
    liste* new_element = malloc(sizeof(struct liste));
    new_element->data = data;
    new_element->suivant = NULL;

    if (l == NULL)
        return new_element;

    for (e = l; e->suivant != NULL; e = e->suivant);

    e->suivant = new_element;
    return l;
}

Là aussi on a remplacé toutes les occurrences de liste* par liste_t. À part ça, rien ne change.

Néanmoins, compiler le programme en l'état ne marchera pas immédiatement. gcc donnera des insultes du style Dereferencing pointer to incomplete type.

Ce que ça veut dire : en compilant main.c, on a inclus liste.h et le typedef struct liste* liste_t. Ainsi, gcc sait qu'un type liste_t existe et que c'est en fait un pointeur vers une struct liste. Mais comme on a caché la définition de la struct liste dans le .c, il ne connaît pas le contenu de la struct, ni sa taille, ni si le membre suivant existe et est bien du type liste_t, ni que pour accéder à ce membre suivant il faut en fait prendre les octets 4 à 7 de la struct (8 à 15 si vous êtes en 64 bits) et les interpréter comme un pointeur… bref, il ne connaît rien de tout cela.

Ce qui pose problème sont la troisième clause de la boucle for (donc e = e->suivant) et l'expression qui permet d'extraire une valeur de la liste, à savoir *(int*)(e->data).

La solution consiste tout simplement à remplacer ces clauses par deux fonctions : une fonction qui renvoie le membre element d'un liste_t et une autre fonction qui renvoie le membre data interprété en tant que int. Il suffit alors de les rajouter dans le header :

liste_t liste_get_suivant(liste_t element);
int liste_get_data_int(liste_t element);

et de les implémenter comme suit :

liste_t liste_get_suivant(liste_t element) {
    return element->suivant;
}

int liste_get_data_int(liste_t element) {
    return *(int*)(element->data);
}

Ce qui nous permet enfin de réécrire la boucle du main.c ainsi :

printf("Voici le contenu de ma liste : \n");
for (e = l; e != NULL; e = liste_get_suivant(e)) {
    printf(" * %d\n", liste_get_data_int(e));
}

Une petite macro pour finir

Si on ajoute dans list.h la macro suivante :

#define liste_foreach(iterator, list) \
    for ((iterator) = (list); \
        ((iterator) != NULL); \
        (iterator) = liste_get_suivant(iterator))

on peut alors écrire la boucle du main.c ainsi :

printf("Voici le contenu de ma liste : \n");

/* pour chaque élément e dans la liste l */
liste_foreach (e, l) {
    printf(" * %d\n", liste_get_data_int(e));
}

Élégant, n'est-ce pas ?

Vers de la programmation orientée objet en C

J'ai loin d'avoir montré toutes les subtilités des types opaques en C, mais je pense que ceux qui ont déjà programmé dans des langages (orientés) objet verront dans les fonctions liste_get_suivant et liste_get_data_int des sortes d'« accesseurs » (un « getter », plus exactement) pour les éléments de notre liste chaînée. Dans notre exemple, nous n'avions pas besoin de plus, mais il serait tout à fait aisé d'imaginer les « setters » et autres « méthodes ».

Si on voulait écraser une valeur dans un élément d'une liste, il suffirait d'écrire une fonction dont le prototype est :

void liste_set_data(liste_t element, void* data);

Ce que j'ai montré ici ressemble donc presque à une sorte de façon détournée de faire de la programmation orientée objet en C. Mais en fait, c'est plus ou moins la façon dont c'est implémenté : en C, il est d'usage que lorsqu'on code une fonction qui opère sur une structure, que le premier paramètre de cette fonction soit cette structure. De la même façon que toute méthode d'une classe reçoit systématiquement comme paramètre caché le pointeur this en C++.

J'en profite enfin pour faire un petit peu de publicité pour le blog de Nausicaa et son article « Le pessismisme français : radiographie d'une représentation ». Un post très intéressant que j'ai lu avec beaucoup de plaisir sur un blog qui cherche à démystifier toute un tas d'idées reçues plus ou moins en lien avec l'actualité.

Profiter de la fibre Orange pour les geeks

Auteur :  x0r Publié le   Nombre de commentaires : 5
Mots-clefs : orange demenagement free fibre ftth cablage

Il y a quelques semaines, j'ai emménagé dans une résidence qui a été fibrée par Orange.

Au début je pensais que cela signifiait que je pouvais continuer à utiliser Free, mais manifestement, l'accord multi-opérateurs qui est nécessaire pour cela n'a pas encore été signé. Malheureusement, cela signifie donc que pour le moment, je serai obligé de passer par le FAI qui a fait le raccordement à la fibre.

C'est pourquoi j'ai décidé de vendre mon âme à Orange (en tant que client mobile et d'ancien stagiaire chez FT R&D c'est déjà chose faite mais bon) pour profiter d'une connexion fibre à 200 Mbps en download et 50 Mbps en upload, une sacrée claque par rapport à l'ADSL2+ dont je profitais avant. Du moins, c'est ce que les différents tests de débit confirment.

Néanmoins, j'ai vu que certains ont mis en place un tunnel GRE entre Orange et un point d'accès chez OVH, ce qui pourrait également se révéler intéressant si ce FAI fait des trucs pas très nets.

L'architecture réseau prévisionnelle

Pour le moment, j'ai mon propre routeur OpenWRT, mais que je laisserai derrière la Livebox. Je sais qu'il y a la possibilité de se passer de la Livebox, mais faute de temps pour faire joujou avec des cartes FXO/FXS et faute de temps pour faire marcher la téléphonie dans ces cas-là, je ne le fais pas encore.

Réseau prévisionnel

En effet, ceux qui n'envisagent pas d'utiliser la télévision ni le téléphone Orange peuvent très simplement utiliser leur propre routeur OpenWRT en lieu et place de la Livebox. L'avantage étant que celui-ci fournit une interface de configuration par SSH et qu'on en garde le contrôle total.

Merci à ack et slash sur IRC pour leurs renseignements, en particulier à propos de l'utilisation de ses propres équipements :

11:31:22 < slash> x0r: j'ai un pfSense à la place de la livebox fibre, 
              ça marche bien, il faut juste que ça gére les VLAN et PPPoE
11:35:22 < slash> x0r: c'est le VLAN 835 pour info :)

Cependant, il est tout à fait possible d'obtenir également la télévision et le téléphone avec cette méthode, mais ce sera un peu plus compliqué et je verrai ça plus tard.

Prérequis

Avant de commencer quoi que ce soit, allez dans l'interface de configuration de la Livebox et donnez une IP fixe à votre routeur OpenWRT. Ajoutez-le également dans la DMZ pour que la Livebox forwarde tous les ports TCP/UDP de la box vers lui.

Connectivité IPv4

Contrairement à leur connectivité IPv6, l'IPv4 passe bien. Seul problème, quelque chose de particulièrement nuisible à tout projet d'auto-hébergement est le fait que les adresses IP sont dynamiques et donc susceptibles de changer d'une minute à l'autre !

Heureusement, il y a des parades tels que le DNS dynamique qui permettent de mettre à jour une entrée DNS ou deux dès qu'une adresse IP change.

Je me suis néanmoins rendu compte de quelque chose d'assez frustrant : lorsqu'on fait pointer une entrée DNS vers l'IP extérieure de la box (par exemple un chezmoi.example.com qui est ensuite DNATé vers un serveur sur lequel on autohéberge un site Web) et qu'on essaye d'accéder à ce chezmoi.example.com depuis l'intérieur via un navigateur, eh bien on obtient... le panneau d'administration de la Livebox. Autrement dit : la Livebox est infoutue de faire du NAT loopback correctement.

La seule mesure palliative à peu près propre à ce "bug" constitue alors à maintenir un DNS "split-horizon", c'est-à-dire maintenir deux examplaires de son domaine example.com. L'une est servie à l'extérieur et fait pointer chezmoi.example.com sur l'IP publique de la box. L'autre n'est accessible que de l'intérieur de son propre LAN et fait pointer chezmoi.example.com sur l'IP du (vrai) serveur à l'intérieur du LAN.

Dans mon cas, j'ai la version publique du domaine x0r.fr chez OVH, et la version privée (avec mes IP privées) du même domaine sur mon serveur sous BIND9.

Connectivité IPv6

Lorsque j'ai eu l'idée saugrenue d'appeler le support technique pour « une question d'ordre technique à propos de [leur] offre Livebox », la conversation téléphonique qui s'en est suivie se résume à cela :

— Bonjour monsieur, est-ce que vous fournissez un préfixe IPv6 à vos clients ?
— Ipévéquoi ?
— IPv6.
(bruits de clavier) Ah oui, pour des connexions à usage professionnel.
— Proposez-vous cela à des particuliers ?
— Non.
— D'accord. (clac)

Conclusion : c'est pas encore pour tout de suite, même si des rumeurs disent que ce sera pour 2014 ou 2015. J'espère juste qu'on ne me mette pas derrière un Carrier-Grade NAT d'ici là. Ceci dit, le carton de la Livebox indique qu'elle est compatible IPv6, donc ils ont au moins prévu une stack IPv6 dans le firmware.

En tout cas, il faudra donc utiliser un tunnel broker, celui de SixXS ou de Hurricane Electric.

Administration de son propre domaine DNS

Nous verrons ici tout ce qu'il reste à faire niveau DNS afin de pouvoir faire de l'auto-hébergement correctement sur sa ligne.

Mise à jour dynamique avec nsupdate

Ce qui est probablement le plus frustrant avec le fait d'avoir une adresse IP dynamique est l'impossibilité de gérer (simplement) une zone DNS ayant des entrées qui pointent vers des machines chez soi.

Heureusement, le paquet ddns-scripts fourni par OpenWRT permet d'utiliser simplement son routeur OpenWRT comme client DynDNS.

Comme le registrar de ma zone DNS est OVH, il me suffisait simplement d'ajouter un champ DYNHOST dans ma zone. Il a fallu que je désactive le "mode avancé" pour ce faire, cependant. Dans le champ "Adresse IP", j'ai saisi l'IP de mon ancienne Freebox pour forcer la première mise à jour.

Ensuite, sur le routeur OpenWRT, rien de très compliqué :

# opkg install ddns-scripts
# vi /etc/config/ddns

Le fichier de config que j'ai utilisé pour OVH est le suivant :

config service "myddns"
        option enabled          "1"
        option interface        "eth0.2"

        option service_name     "ovh.com"
        option domain           "dyn.example.com"
        option username         "example.com-monusername"
        option password         "monpassword"

        option force_interval   "24"
        option force_unit       "hours"
        option check_interval   "10"
        option check_unit       "minutes"
        option retry_interval   "60"
        option retry_unit       "seconds"

        option ip_source        "web"
        option ip_url           "http://checkip.dyndns.com/"

Cette config permet de mettre à jour un domaine dyn.example.com. Comme ça, tous les noms qui devraient pointer vers l'adresse IP de la Livebox suffisent d'être modifiés pour qu'ils deviennent des CNAME ("alias") vers ce dyn.example.com et on n'aura pas besoin de refaire cette configuration cinquante mille fois.

Il y a néanmoins un cas où il m'a fallu rajouter une entrée quasiment identique à ce que j'ai montré : lorsqu'on héberge un serveur mail derrière sa box. Du coup j'ai aussi une autre section config service dans /etc/config/ddns qui sert à mettre à jour un mx.example.com. En effet, Les entrées DNS des serveurs mail devant impérativement être de type A ou AAAA et n'ont donc pas le droit d'être des CNAME.

Ensuite, deux solutions pour démarrer le service. Comme cela ne se fait pas via un initscript mais que ce service démarre automatiquement lors du démarrage de l'interface associée, on a le choix entre rebooter (mais ça, c'est pour les faibles) ou démarrer manuellement à l'aide de la commande :

# ACTION=ifup INTERFACE=eth0.2 /sbin/hotplug-call iface

Pour que les champs DynHost fonctionnent correctement, il m'a néanmoins fallu désactiver de manière permanente le mode avancé (celui où on modifie la zone DNS dans un éditeur de texte, comme un vrai). Un léger inconvénient mais un mal nécessaire pour que les mises à jour fonctionnent.

E-mails sortants : le relais d'Orange à moitié caché

Comme j'avais configuré mon Postfix pour qu'il envoie des mails via le relais mail de Free, je ne peux désormais plus envoyer de mails vers l'extérieur sans un brin de configuration.

Avant de commencer, comme mon serveur mail est un MX principal et que son IP va changer, j'ai viré la clause proxy_interfaces (qui ne servait à rien de toute façon parce que mon serveur n'est pas un MX secondaire).

Ensuite, il suffit d'utiliser le relais SMTP d'Orange. En gros, voici ce que j'ai désormais dans le main.cf de Postfix:

relayhost                       = [193.252.22.72]:587

smtp_sasl_auth_enable           = yes
smtp_sasl_password_maps         = hash:/etc/postfix/sasl_passwd

smtp_tls_CAfile                 = /etc/ssl/postfix/gmail_cert.pem
smtp_tls_cert_file              = /etc/ssl/postfix/server.crt
smtp_tls_key_file               = /etc/ssl/postfix/server.pem
smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_tls_session_cache
smtp_use_tls                    = yes
smtp_sasl_security_options      = noanonymous
smtp_sasl_tls_security_options  = noanonymous
smtp_tls_note_starttls_offer    = yes
tls_random_source               = dev:/dev/urandom
smtp_tls_scert_verifydepth      = 5
smtp_tls_enforce_peername       = no
smtp_tls_CApath                 = /etc/ssl/certs

Pas mal de trucs datent encore d'une vieille config où j'utilisais Gmail comme relais, et je n'ai pas eu le temps de tester si ces éléments-là sont encore pertinents. (surtout que bon, comme ils refilent tout à la NSA, je ne devrais plus leur faire confiance... mais je suis peut-être mauvaise langue.)

Ne pas oublier de créer le fichier /etc/postfix/sasl_passwd qui contient les identifiants messagerie. Le login est la partie avant @orange.com. Donc si Orange vous a donné une adresse e-mail truc.bidule@orange.com et que le password est supersecret, le fichier contiendra donc :

[193.252.22.72]:587     truc.bidule:supersecret

Enfin, ne pas oublier non plus d'exécuter la commande

# postmap /etc/postfix/sasl_passwd

pour que Postfix tienne compte du password. Un chmod 600 sur ce fichier, ainsi que le sasl_passwd.db produit par la commande postmap(1), n'est probablement pas une mauvaise idée non plus. Rechargez la configuration de Postfix puis testez l'envoi d'un petit mail vers une adresse "extérieure" à votre domaine, genre une adresse Gmail ou Yahoo!.

Remarquez aussi qu'on n'a pas donné de noms DNS pour le relais SMTP d'Orange. Son petit nom est en réalité smtp-msa.orange.fr mais ne peut être résolu qu'en utilisant les DNS d'Orange. Je n'ai pas encore regardé ce que valent les DNS d'Orange, mais comme je ne les utilise pas (encore), je suis obligé de mettre son adresse IPv4 à la place.

Adieu le reverse DNS customisé

Probablement la feature qui me manque le plus depuis que je n'ai plus Free. Hélas, ce reverse DNS n'est pas paramétrable. Sur IRC, il vous faudra donc clamer haut et fort malgré vous le fait d'être sur une connexion Orange. Au lieu d'avoir

--> Poiral (Poiral@mon.super.rever.se) has joined #truc

vous aurez donc :

--> Poiral (Poiral@AMontsouris-puis-plein-de-chiffres.abo.wanadoo.fr) has joined #truc

Au moins, si vous faites tourner un oidentd sur votre bécane, vous n'aurez pas le ~ devant le hostmask.

Conclusion

Après quelques semaines d'utilisation, je trouve que la fibre optique est une sacrée bouffée d'air frais, en particulier pour YouTube où j'ai limite l'impression d'être en réseau local. Avoir 50 Mbps de débit en upload ouvre énormément de possibilités d'auto-hébergement également. C'est juste un peu dommage qu'Orange ne soit pas le fournisseur d'accès le plus "geek-friendly", mais pour le moment il fait largement l'affaire. Pour le reste, on verra bien.

monrer.fr : autocomplétion et reverse-engineering

Auteur :  x0r Publié le   Nombre de commentaires : 6

Il y a environ deux semaines, j'ai décidé de bouger le site monrer.fr sur un jail FreeBSD (merci KissCool pour ce jail, d'ailleurs). En effet, comme ce site tournait sur le même serveur que x0r.fr et qu'il s'agit d'une machine hébergée derrière une Freebox, un déménagement imminent signifierait au moins 72 heures de downtime, et étant donné la (modeste) fréquentation du site, je ne pouvais plus me permettre cela.

Je vais parler un peu des principales évolutions que j'ai faites ou qui sont en cours de développement.

Balbutiements de mécanisme d'autocomplétion

J'en ai aussi profité pour mettre en place un système d'autocomplétion pour remplacer le menu déroulant de sélection de gare. Il permet de taper aussi bien les premières lettres d'un nom de gare qu'un code TR3 :

Autocomplétion sur monrer.fr

Je suis assez mitigé là-dessus cependant. Je pensais pouvoir faire en sorte de conserver le menu déroulant sur des appareils mobiles et utiliser le champ de texte avec autocomplétion sur les navigateurs « desktop », mais je n'ai pas eu le temps de faire quelque chose de propre qui ne fasse pas déconner certains moteurs JavaScript mobiles (bonjour Opera Mini !).

Néanmoins, le tutoriel autocomplétion en AJAX de Denis Cabasson que j'ai suivi pour en ajouter une sur le site m'a été très utile et est très bien fait. Je trouve que c'est beaucoup plus éducatif d'essayer de le faire soi-même, plutôt que de prendre une random bibliothèque qui le fait déjà.

Une nouvelle source de données

La source de données trafic dont je parlais dans mon premier article à propos de monrer.fr est de moins en moins fiable, et je souhaitais pouvoir le remplacer avec autre chose pour plusieurs raisons :

  • les horaires du RER A et B sont les horaires théoriques et reflètent donc mal la réalité ;
  • il manquait les gares de certaines branches du RER A ;
  • il n'y a pas la possibilité de récupérer les messages qui défilent dans le bandeau en bas des écrans Infogare (donc pas d'infos trafic étendues ni d'infos travaux) ;
  • enfin, le serveur me balance une erreur 500 trois fois sur quatre lorsque je l'interroge.

C'est pourquoi je me suis mis en tête de reverse engineerer l'application Android officielle de Transilien afin de voir comment il fonctionne, et voir si celui-ci utiliserait une autre API en ligne non documentée.

Le temps que la SNCF mette en place une API accessible à tous, qui permette d'obtenir les horaires de toutes les lignes à raison de plus de dix fois par minute et sans authentification HTTP, j'ai constaté la chose suivante :

% dig api.transilien.com

; (snip)

;; ANSWER SECTION:
api.transilien.com.     600     IN      CNAME   api.ode.tn.ocito.com.
api.ode.tn.ocito.com.   86400   IN      A       85.233.209.218

Manifestement, le développement de l'API proposée par la SNCF a été déléguée par un sous-traitant, OCITO, ce qu'on retrouve assez facilement dans le nom des classes Java une fois le .apk de l'application Android officielle téléchargé, dézippé et décompilé avec dexdump. Assez étrange, dans le sens où l'entreprise se présente plus comme une entreprise de marketing mobile plutôt qu'une boîte qui aurait tout plein de datacenters capables de gérer tous les appels d'API d'usagers qui se demanderaient où diable se trouve leur RER, mais là n'est pas la question.

Passage supprimé à la demande de la SNCF.

Dissection de l'API OCITO

Passage supprimé à la demande de la SNCF.

Obtenir les prochains trains

Passage supprimé à la demande de la SNCF.

Le serveur renvoie [...], en JSON, la liste des prochains trains avec leur code mission, leur numéro, le terminus et l'heure de passage. Il ne donne cependant pas la liste des gares desservies, que je dois obtenir séparément et individuellement pour chaque train. Cependant, l'API fournit également cette information. Dans la même réponse figure une liste de chaînes de caractères correspondant aux informations affichées en bas des écrans Infogare.

Obtenir la desserte d'un seul train

Passage supprimé à la demande de la SNCF.

Le serveur renvoie alors la liste des gares ainsi que l'heure de passage, ce qui me permet d'obtenir à nouveau les informations de trafic "live" que j'obtenais jadis de sncf.mobi.

Conclusion

Avant monrer.fr, j'avais codé un plug-in pour un bot IRC qui récupérait le même genre d'informations que ce que je fais maintenant. Ma toute première approche consistait à parser le code HTML du site Transilien à l'aide de la page qui affichait toutes ces informations. Il s'agit donc maintenant de la troisième fois que je recherche une méthode pour obtenir ces horaires en temps réel, et le temps que la SNCF mette au point une API pour les obtenir qui me soit accessible, j'espère que ma méthode fera l'affaire.

En tout cas, des mises à jour sont à prévoir sur monrer.fr, même si je risque d'être assez occupé avec un déménagement imminent.

J'en profite aussi pour remercier tous ceux qui ont utilisé ce site et qui le trouvent utile, et plus particulièrement ceux qui envoient des bugreports.