[spip-dev] Spip vs la Chine

Bonjour,

J'administre un site hébergé en Chine continentale (par nécessité absolue).

Depuis le 23 septembre dernier, les autorités chinoises ont imposé à tous les hébergeurs et fournisseurs d'accès d'installer un système de filtrage supplémentaire du net baptisé "blue dam".

Il se trouve que ce système rend la syndication entrante de Spip inopérante dans son ensemble : tous les flux entrants sont en "problème de syndication", qu'il s'agisse de RSS standard ou de flux météo appelé par Rainette qui reste vide.

J'ai installé un spip propre sans aucun plugin sur deux hébergeurs différents + deux serveurs locaux sur connexions privées, rien à faire, il est devenu impossible de syndiquer des flux entrants avec Spip.

J'ai tracé l'erreur jusqu'à la fonction recuperer_entetes de distant.php qui renvoit toujours 0, et plus particulièrement à la ligne 306 :

$s = @trim(fgets($f, 16384));

$f vaut "Ressource ID #1334" ce qui semble normal, mais fgets($f, 16384) ne renvoie jamais rien.

Par ailleurs j'ai fait un simple test en php : file_get_contents('url') passe quant à lui sans problème les filtres en question.

En utilisant un proxy hors de Chine, le problème disparaît immédiatement, mais le site se met immédiatement à ramer du fait de l'engorgement continuel des diverses couches de filtrage permettant de sortir de "l'intranet" chinois.

Donc quelqu'un a-t-il une idée de comment contourner ce problème de fonction fgets inopérante ? Y-a-t-il des solutions simples ? Des jeux de test que je pourrais mettre en place pour débugger ça ?

Merci d'avance

A bientôt
    Simon

Depuis le 23 septembre dernier, les autorités chinoises ont imposé à tous les hébergeurs et fournisseurs d'accès d'installer un système de filtrage supplémentaire du net baptisé "blue dam".

..

J'ai tracé l'erreur jusqu'à la fonction recuperer_entetes de distant.php qui renvoit toujours 0, et plus particulièrement à la ligne 306 :

$s = @trim(fgets($f, 16384));

$f vaut "Ressource ID #1334" ce qui semble normal, mais fgets($f, 16384) ne renvoie jamais rien.

Par ailleurs j'ai fait un simple test en php : file_get_contents('url') passe quant à lui sans problème les filtres en question.

Si j'ai bien compris tout ce que tu décris, ce patch devrait débloquer la situation:

@@ -226,9 +226,13 @@
         else {
                 $headers = recuperer_entetes($f, $date_verif);
                 if (is_numeric($headers)) {
- spip_log("HTTP status $headers pour $url");
                         fclose($f);
- return false;
+ if ($headers) {
+ spip_log("HTTP status $headers pour $url");
+ return false;
+ } elseif ($result = file_get_contents($url))
+ return array('', $result);
+ else return false;
                 }

Committo,Ergo:Sum

Committo,Ergo:sum a écrit :

Si j'ai bien compris tout ce que tu décris, ce patch devrait débloquer la situation:

@@ -226,9 +226,13 @@
        else {
                $headers = recuperer_entetes($f, $date_verif);
                if (is_numeric($headers)) {
- spip_log("HTTP status $headers pour $url");
                        fclose($f);
- return false;
+ if ($headers) {
+ spip_log("HTTP status $headers pour $url");
+ return false;
+ } elseif ($result = file_get_contents($url))
+ return array('', $result);
+ else return false;
                }

Bonjour et merci.

Ca a solutionné la première partie du problème (j'ai ajouté un @ pour éviter le warning quand le fichier est absent), mais ça en a révélé un nouveau : apparemment les opérateurs du réseau chinois se sont mis en tête de filtrer les user agent des requêtes selon une liste blanche...

Pour info : Une requête émise par un navigateur ayant un user agent "habituel" sur un fichier passe sans problème, par contre un serveur apache en config par défaut déclenche les filtres à savoir :
- envoi de reset aux deux bouts de la connexion
- blocage de la page cible pour l'ip source pendant une minute (et ce quel que soit le user agent, si on essaie ensuite d'y accéder à nouveau par le même navigateur, ça reset)
Je savais que la majorité des serveurs web Chinois sont sous IIS (les licences ne sont "pas chères" et la fiabilité optionnelle, ici :slight_smile:

Je vais chercher comment faker le user agent sur mon serveur.

C'est bô la liberté, vivement qu'on ait la même chose en France... :confused:

A bientôt
    Simon

Committo,Ergo:sum a écrit :

Si j'ai bien compris tout ce que tu décris, ce patch devrait débloquer la situation:

@@ -226,9 +226,13 @@
        else {
                $headers = recuperer_entetes($f, $date_verif);
                if (is_numeric($headers)) {
- spip_log("HTTP status $headers pour $url");
                        fclose($f);
- return false;
+ if ($headers) {
+ spip_log("HTTP status $headers pour $url");
+ return false;
+ } elseif ($result = file_get_contents($url))
+ return array('', $result);
+ else return false;
                }

Bonjour (enfin, re),

Après pas mal de recherche et de bidouillage, j'ai fini par arriver à faire remarcher la syndication entrante sur le réseau chinois.
J'ai rencontré 3 niveaux de blocage :

*Corrections appliquées *(Base de travail : Spip 2.0.10)
- Le premier est un blocage apparemment aléatoire de fgets, le correctif que tu m'as donné permet de rajouter une deuxième chance si ça ne passe pas.
- Deuxième niveau de filtrage sur le http_user_agent envoyé au moment de la connexion
     => Par défaut Spip s'identifie en tant que "SPIP-2.0.10 (http://www.spip.net)", il faut le déguiser en navigateur classique, par exemple
    "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4"
    (ce correctif étant intégré au suivant, voir ci-dessous)
   - Il faut enfin forcer la connexion en HTTP 1.1 avec les en-têtes forgés repris d'une connexion "classique" par Firefox
    Cela demande de modifier la déclaration de deux fonctions dans le même fichier distant.php :
    * init_http : (ligne 545) remplacer $vers="HTTP/1.0" par $vers="HTTP/1.1"dans la liste des paramètres
    * lance_requete : (ligne 580) remplacer $vers="HTTP/1.0" par $vers="HTTP/1.1"dans la liste des paramètres,
        puis refaire complètement la déclaration de la requête (à partir de la ligne 602)
        remplacer :
              $req = "$method $path $vers\r\n"
            . "Host: $host\r\n"
            . "User-Agent: SPIP-".$GLOBALS['spip_version_affichee']."(http://www.spip.net/)\r\n <http://www.spip.net/)\r\n&gt;&quot;
            . ($refuse_gz ? '' : "Accept-Encoding: gzip\r\n")
            . (!$site ? '' : "Referer: $site/$referer\r\n")
            . (!$user ? '' : "Authorization: Basic "
            . base64_encode(urlencode($user[0]).":".urlencode($user[1]))."\r\n")
            . (!$proxy_user ? '' :
                ("Proxy-Authorization: Basic "
                 . base64_encode($proxy_user . ":" . $proxy_pass) . "\r\n"));
        par :
            $req = "$method $path $vers\r\n"
            . "Host: $host\r\n"
            . "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4\r\n"
            . ($refuse_gz ? '' : "Accept-Encoding: gzip,deflate\r\n") //ça coupe à 100% si on oublie deflate !
            . (!$site ? '' : "Referer: $site/$referer\r\n")
            . (!$user ? '' : "Authorization: Basic "
            . base64_encode(urlencode($user[0]).":".urlencode($user[1]))."\r\n")
            . (!$proxy_user ? '' :
                ("Proxy-Authorization: Basic "
                 . base64_encode($proxy_user . ":" . $proxy_pass) . "\r\n"))
            . "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/"."*;q=0.8\r\n"
            . "Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.6,en;q=0.4,zh-cn;q=0.2\r\n"
            . "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
            . "Keep-Alive: 300\r\n"
            . "Connection: keep-alive\r\n\r\n";

Je sais : j'ai fait ça salement en prenant pour valeurs ce que j'ai intercepté dans une requête firefox classique car je n'ai pas le temps de conduire des tests complets pour savoir quels paramètres et valeurs sont obligatoires, d'autant plus que ça n'est évidemment pas documenté (ni même reconnu !), et que ça change tout le temps au gré des humeurs politiques et techniques (les filtres sont apparemment codés avec les coudes et l'infrastructure est instable).

Suite à ce correctif, nous avons récupéré la météo et la plupart des flux rss entrants (quelques-uns résistent, mais je n'ai pas la moindre idée de comment contourner le blocage dans leur cas).
A bientôt
    Simon

Après pas mal de recherche et de bidouillage, j'ai fini par arriver à faire remarcher la syndication entrante sur le réseau chinois

...

      par :
          $req = "$method $path $vers\r\n"
          . "Host: $host\r\n"
          . "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4\r\n"
          . ($refuse_gz ? '' : "Accept-Encoding: gzip,deflate\r\n") //ça coupe à 100% si on oublie deflate !
          . (!$site ? '' : "Referer: $site/$referer\r\n")
          . (!$user ? '' : "Authorization: Basic "
          . base64_encode(urlencode($user[0]).":".urlencode($user[1]))."\r\n")
          . (!$proxy_user ? '' :
              ("Proxy-Authorization: Basic "
               . base64_encode($proxy_user . ":" . $proxy_pass) . "\r\n"))
          . "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/"."*;q=0.8\r\n"
          . "Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.6,en;q=0.4,zh-cn;q=0.2\r\n"
          . "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
          . "Keep-Alive: 300\r\n"
          . "Connection: keep-alive\r\n\r\n";

Je sais : j'ai fait ça salement

Je viens de tester ton code chez moi voir comment l'intégrer proprement.
Ce qui est étrange, c'est qu'en cas de réception compressée, la fonction PHP gzfile n'arrive pas à décompresser la réponse,
et cela qu'il y ait un seulement "gzip" dans l'en-tête Accept-Encoding (et là ça devrait fonctionner) ou "gzip,deflate"
(et là on peut comprendre que ça ne marche pas car gzfile est pour gzip pas pour deflate).
Quelle expérience précise as-tu là-dessus car ton commentaire est obscur: ça veut dire quoi "couper à 100%" ?

Committo,Ergo:Sum

En fait, ce n'est qu'en HTTP1.1 que le pb se pose c'est bizarre.
Prend:
http://trac.rezo.net/trac/spip/changeset/14715
et définis les 3 constantes du début de fichier selon tes besoins.
Il manque certains des en-têtes que tu as donnés,
mais on peut penser qu'ils ne sont pas utiles.

Committo,Ergo:Sum

Committo,Ergo:sum a écrit :

Je viens de tester ton code chez moi voir comment l'intégrer proprement.
Ce qui est étrange, c'est qu'en cas de réception compressée, la fonction PHP gzfile n'arrive pas à décompresser la réponse,
et cela qu'il y ait un seulement "gzip" dans l'en-tête Accept-Encoding (et là ça devrait fonctionner) ou "gzip,deflate"
(et là on peut comprendre que ça ne marche pas car gzfile est pour gzip pas pour deflate).
Quelle expérience précise as-tu là-dessus car ton commentaire est obscur: ça veut dire quoi "couper à 100%" ?

En fait, ce n'est qu'en HTTP1.1 que le pb se pose c'est bizarre.
Prend:
http://trac.rezo.net/trac/spip/changeset/14715
et définis les 3 constantes du début de fichier selon tes besoins.
Il manque certains des en-têtes que tu as donnés,
mais on peut penser qu'ils ne sont pas utiles.

Committo,Ergo:Sum

Merci, ça semble marcher au moins aussi bien que mon patch crade (mais en plus propre, oeuf corse).

Cependant, j'ai remarqué que, si la récupération initiale passe, le domaine cible est ensuite bloqué pour 60 à 90 secondes pour l'ip source de la requête.
C'est ennuyeux quand on a des scripts qui se connectent plusieurs fois d'affilée à la même source de syndication pour récupérer des données différentes (exemple : rainette quand on affiche les conditions actuelles + la météo à 4 jours ou encore des pages de syndications multi-thématiques provenant d'une même source).

D'ailleurs ça soulève un point sur lequel je m'interroge depuis longtemps : pourquoi, en cas de problème de syndication, spip décide que l'objet dépendant du flux qui pose problème ne doit plus être affiché ?
Pourquoi ne pas afficher la dernière version récupérée plutôt que cette désactivation brutale ?
Les informations sont pourtant dans la base pour ce qui est des syndic_articles, ou dans le cache xml pour rainette.
Dans les pays où la connectivité inter-sites est aléatoire comme ici, en Chine, ça simplifierait beaucoup le travail des webmestres...

A bientôt
    Simon

Committo,Ergo:sum a écrit :

Je viens de tester ton code chez moi voir comment l'intégrer proprement.
Ce qui est étrange, c'est qu'en cas de réception compressée, la fonction PHP gzfile n'arrive pas à décompresser la réponse,
et cela qu'il y ait un seulement "gzip" dans l'en-tête Accept-Encoding (et là ça devrait fonctionner) ou "gzip,deflate"
(et là on peut comprendre que ça ne marche pas car gzfile est pour gzip pas pour deflate).
Quelle expérience précise as-tu là-dessus car ton commentaire est obscur: ça veut dire quoi "couper à 100%" ?

En fait, ce n'est qu'en HTTP1.1 que le pb se pose c'est bizarre.
Prend:
http://trac.rezo.net/trac/spip/changeset/14715
et définis les 3 constantes du début de fichier selon tes besoins.
Il manque certains des en-têtes que tu as donnés,
mais on peut penser qu'ils ne sont pas utiles.

Committo,Ergo:Sum

Pardon, j'ai parlé trop vite, il prenait encore en compte mon ancienne correction à la place de la tienne... qui est en fait systématiquement bloquée :frowning:

Voilà la requête produite par ton code, qui déclenche le filtre et qui renvoie un beau "connexion reset" :
    GET /fr/une/actus/alaune.xml HTTP/1.1
    Host: www.cnrs.fr
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4
    Accept-Encoding: gzip,deflate
    Referer: http://127.0.0.1/site_local/
    Keep-Alive: 300
    Connection: keep-alive

Voilà la requête produite par mon patch pourri d'hier qui passe (mais qui empêche ensuite le client d'accéder au même domaine pendant 90 secondes) :
    GET /fr/une/actus/alaune.xml HTTP/1.1
    Host: www.cnrs.fr..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4
    Accept-Encoding: gzip,deflate
    Referer: http://127.0.0.1/site_local/
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.6,en;q=0.4,zh-cn;q=0.2
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 300
    Connection: keep-alive

Les seules différences sont :
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.6,en;q=0.4,zh-cn;q=0.2
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7

Je suis incapable de déterminer quelle partie de ce code est nécessaire pour ne pas être détecté comme dangereusement contre-révolutionnaire.
Tous les essais que j'ai faits jusqu'ici entraînent le blocage immédiat, c'est ce que je voulais dire dans mon commentaire sur deflate.
Et comme je l'ai précisé, en raison du blocage pour 90 s, les tests sont longs et fastidieux, je n'ai pas le temps de les conduire d'un bout à l'autre pour le moment, je suis obligé d'aller à l'essentiel.
En plus de ça, ça change sans arrêt du jour au lendemain (c'est arrivé plusieurs fois dans les derniers mois) et il n'y a aucune documentation, donc si ça se trouve, demain ça sera autre-chose...

Merci beaucoup pour le coup de main, en tout cas !

A bientôt
    Simon

Bonjour,

Après de nombreux tests, je me suis rendu compte que les filtres chinois sont super capricieux et très susceptibles envers les serveurs web qui récupèrent des données de syndication (bref tout ce qui est plus ou moins deux point zéro), particulièrement pour spip mais je ne sais pas pourquoi.

L'effet principal de ce filtrage est que, même quand ça passe, le délai de récupération du fichier xml cible est TRES long (de l'ordre de 15 secondes).

Ce qui m'a permis au passage de constater que rainette récupère visiblement les flux de weather.com au moment du calcul de la page => ça fait méchamment ramer le pauvre visiteur malchanceux ou le dev qui fait son recalcul. Ne serait-il pas préférable de passer ça dans le CRON pour rendre cette récupération moins dommageable à l'expérience utilisateur en cas de connectivité faible, vu que les xml sont mis en cache ?

D'ailleurs, comment font les autres flux de syndication ? C'est le cron qui s'en charge ou là aussi ça se passe au calcul de la page ?

Pour ma part, j'ai finalement décidé de contourner le problème chinois en essayant de créer un script appelé par le cron système pour récupérer les fichiers distants et les placer dans un répertoire cache local.
Il ne restera plus qu'à intercepter les appels de spip à des fichiers distants, mais ça je sais où ça se trouve.

C'est bourrin, mais ça devrait marcher, plus d'infos dans quelques temps si j'y arrive.

A bientôt
    Simon

Bonjour,

Je ressuscite ce fil afin de faire part aux éventuels malchanceux qui sont dans le même cas que moi des solutions que j'ai trouvées.

Je rappelle rapidement le problème :
- depuis l'installation par le gouvernement chinois du système de filtrage "Blue Dam", la syndication de spip ne passe plus pour les sites hébergés en Chine continentale, aussi bien les RSS que les flux météo de Rainette.
- des petites bidouilles (en-têtes http forgés, voir messages précédents) permettent aux requêtes de passer une fois sur 3
- inconvénient : quand ça ne passe pas, ça lag un max, dégradant l'expérience de l'utilisateur malchanceux qui tombe sur le calcul de la page contenant le blocage en question

Solution trouvée :

C'est un peu bourrin, mais j'ai écrit un petit script php appelé par le cron unix toutes les 10 minutes qui va prendre les fichiers voulus et qui les copie en local dans tmp/cron_syndic
Ensuite j'ai forké rainette/inc/rainette_utils pour que le plugin aille lire dans ce répertoire les fichiers enregistrés plutôt que les versions distantes sur weather.com
Depuis, plus de lag, plus de perte de météo sur le site, c'est le cron qui prend les inconvénients mais il s'en fout, lui ! Au pire, elle est un peu obsolète.

Je vais maintenant chercher ce qu'il faut forker pour changer également le comportement des syndications RSS de spip.
Décidément, la censure n'embêtera toujours que les honnêtes gens...

A bientôt
    Simon

Committo,Ergo:sum a écrit :

c'est ce pourquoi elle existe depuis tout temps, "c'est pour leur bien" :slight_smile:
Claude