Category Archives: Système

Controler les core dump

La génération des fichier core contiennent l’état de la mémoire d’un process, et a lieu généralement suite au crash de ce process. Sous Linux, le core dump est provoqué suite à la réception par le process de signaux spécifiques documentés dans le man 7 signal:

       Signal     Value     Action   Comment
       ----------------------------------------------------------------------

       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGSEGV      11       Core    Invalid memory reference

       SIGBUS      10,7,10     Core    Bus error (bad memory access)
       SIGSYS      12,31,12    Core    Bad argument to routine (SVr4)
       SIGTRAP        5        Core    Trace/breakpoint trap
       SIGXCPU     24,24,30    Core    CPU time limit exceeded (4.2BSD)
       SIGXFSZ     25,25,31    Core    File size limit exceeded (4.2BSD)

Avant toute chose, il faut vérifier que le système est capable de générer ces fichiers. En effet, sous Ubuntu par exemple, la user limit (ulimit) pour la taille des fichiers core est nulle. Il faut donc permettre au système d’écrire des fichiers de taille illimité:

$ ulimit -c unlimited

On peut trivialement vérifier que les fichiers core sont générés. On lance un shell, et on envoie un signal SIGSEGV qui va faire crasher ce nouveau process ($$) pour générer le core:

$ bash
$ kill -SEGV $$
Segmentation fault (core dumped)
$ ls -l core
-rw------- 1 mycroft mycroft 3784704 2011-04-02 13:02 core

Cependant, parfois, on veut pouvoir controler un peu plus ce fichier, en particulier son nom et sa destination. Les noyaux Linux 2.6 permettent cela grace à la variable système kernel.core_pattern (ou /proc/sys/kernel/core_pattern) qui sert de template pour le nom de fichier et qui a comme valeur par défaut « core ». Ce template accepte un chemin absolu et les modificateurs de format suivant:

  • %p – pid
  • %u – uid
  • %g – gid
  • %s – numéro de signal
  • %t – temps UNIX / timestamp
  • %h – hostname
  • %e – nom de fichier executable
  • %% – « % »

On décide donc de placer nos fichiers core dans /tmp/core, et qu’ils aient le format « timestamp.nom-executable.pid.uid:gid ». Pour cela, on modifie /proc/sys/kernel/core_pattern avec le format de notre choix:

# mkdir /tmp/core/
# echo "/tmp/core/%t.%e.%p.%u:%g" > /proc/sys/kernel/core_pattern

Et on teste:

$ ulimit -c unlimited
$ bash
$ kill -SEGV $$
Segmentation fault (core dumped)
$ ls -l /tmp/core
total 3584
-rw------- 1 mycroft mycroft 3784704 2011-04-02 13:16 1301778979.bash.19366.1000:1000

Il ne reste plus qu’à le configurer de manière à toujours reproduire ce comportement, en particulier après un reboot dy système. Pour cela, il n’y a qu’à ajouter la ligne suivante dans le fichier /etc/sysctl.conf:

kernel.core_pattern=/tmp/core/%t.%e.%p.%u:%g

De plus, pour conserver la valeur de ulimit, il faudra modifier /etc/security/limits.conf et affecter la valeur « unlimited » à l’item « core »:

#<domain>      <type>  <item>         <value>
*              soft    core           unlimited

A noter que cette modification prendra effet au prochain log-in et non à l’ouverture d’une nouvelle console dans X.

Se connecter proprement à ssh via un proxy web

Cet article est issu d’une précédente version de mon weblog, mais perdu dans l’oubli. Il remonte à Avril 2008. Je le remets ici à jour.

Un problème au taf, si ce n’est le prix du café au distributeur, est la non possibilité de se connecter sur sa box avec son ssh. Et bien, Dag Wiers propose une solution propre (qui n’oblige pas à installer son sshd sur le 443 et d’utiliser corkcrew), mais d’utiliser proxytunnel qui marche plutot bien.

Pour résumer la chose, cela implique d’avoir un serveur avec un apache où on a la main dessus, mod_proxy/mod_proxy_connect et mod_proxy_http d’installés, possiblement mod_ssl (mais ça crée un petit problème de taille à cause d’une non-feature d’apache2), et un peu de configuration.

N’ayant pas la main sur mon serveur au taf, c’est pas facile pour tester. J’ai donc aussi installé privoxy en local pour faire mes tests. Par contre, après utilisation, j’ai pas réussi à faire passer un CONNECT sur un port de destination 80, privoxy ne voulait pas, et le limit-connect semblait pas vouloir être changé. En même temps il était 5h du mat, j’ai peut être oublié un truc.

Alors, voilà la conf que j’ai faite sur ma debian (serveur ssh):

Server ssh sur le 22 et 2222;
Apache2 de base installé (bon, avec php mais là on s’en fiche);

On configure le bouzin:

remote# a2enmod ssl
remote# a2enmod proxy
remote# a2enmod proxy_connect

Il faut générer un certificat (.pem) pour notre nouveau serveur https. Pour çà, y a un super outil dans debian, et c’est make-ssl-cert:

remote# make-ssl-cert /usr/share/ssl-cert/ssleay.cnf /etc/apache2/ssl/apache.pem
remote# chmod a+r /etc/apache2/ssl/apache.pem

Et un virtualhost sur le 443, qui inclue directement les directives pour mod_ssl et mod_proxy:

<VirtualHost spine.minithins.net:443>
  SSLEngine On
  SSLCertificateFile /etc/apache2/ssl/apache.pem

  ServerAdmin mycroft@minithins.net
  DocumentRoot /home/web/minithins.net
  ServerName cns.minithins.net

  HostnameLookups On

  ProxyRequests on
  AllowCONNECT 2222
  ProxyVia on

  <proxy *>
    Order deny,allow
    Deny from all
    Allow from client.monkeyz.eu
  </proxy>
</VirtualHost>

Un redémarrage d’apache, et on va tester avec stunnel:

remote# service apache2 restart
Restarting web server: apache2 ... waiting .
client# apt-get install stunnel
...
client# stunnel -f -c -d 12345 -r spine.minithins.net:443
2011.04.02 12:22:22 LOG5[11844:140613013587712]: stunnel 4.29 on x86_64-pc-linux-gnu with OpenSSL 0.9.8o 01 Jun 2010
2011.04.02 12:22:22 LOG5[11844:140613013587712]: Threading:PTHREAD SSL:ENGINE Sockets:POLL,IPv6 Auth:LIBWRAP
2011.04.02 12:22:22 LOG5[11844:140613013587712]: 500 clients allowed
...

Sur une autre console:

client# telnet localhost 12345
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
CONNECT spine.minithins.net:2222 HTTP/1.0

Connection closed by foreign host.

Hum. Ca ne fonctionne pas.

Un peu de recherche (ou de triche, car Dag Wieers donne la réponse), Apache, ou plus précisement le mod_proxy_connect est buggé. Mais un patch existe. Il faut appliquer un des patchs proposés (surement le dernier en date, dans mon exemple celui pour 2.2.16), et lancer un coup de rebuild des packages apache via les scripts debian:

remote# cd /tmp
remote# apt-get install devscripts fakeroot gcc bzip2 dpkg-dev
...
remote# apt-get build-dep apache2.2-common
...
remote# apt-get source apache2.2-common
...
remote# cd apache2-2.2.16/modules/proxy
remote# wget -O mod_proxy_connect.diff "https://issues.apache.org/bugzilla/attachment.cgi?id=26225"
remote# patch -p0 < mod_proxy_connect.diff

… et c’est parti pour tout rebuilder: …

remote# debuild -us -uc
remote# cd /tmp/apache2-2.2.16
... (long) ...

… et finalement on met à jour le package apache2.2-bin:

remote# dpkg -i ../apache2.2-bin_2.2.16-6+squeeze1_amd64.deb
(Reading database ... 48179 files and directories currently installed.)
Preparing to replace apache2.2-bin 2.2.16-6+squeeze1 (using .../apache2.2-bin_2.2.16-6+squeeze1_amd64.deb) ...
Unpacking replacement apache2.2-bin ...
Setting up apache2.2-bin (2.2.16-6+squeeze1) ...
Processing triggers for man-db ...
remote # service apache2 restart
Restarting web server: apache2 ... waiting .

On reteste avec stunnel:

client# telnet localhost 12345
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
CONNECT spine.minithins.net:2222 HTTP/1.0

HTTP/1.0 200 Connection Established
Proxy-agent: Apache/2.2.16 (Debian)

SSH-2.0-OpenSSH_5.5p1 Debian-6

Yay ! First step donne. Plus qu’à tester via un proxy http et utiliser le client ssh.

Pour tester, j’installe en local un privoxy:

client# apt-get install privoxy

On pourra modifier l’adresse d’écoute de privoxy (localhost:8118), et le modifier (par exemple, 127.0.0.1:8080 pour éviter qu’il prenne l’adresse ipv6 au lieu de la v4).

On crée un profil ssh en accord, dans ~/.ssh/config:

Host spine-proxy
    DynamicForward 1080
    ServerAliveInterval 30
    # proxy.dev pointe vers localhost car mon proxy est sur localhost.
    ProxyCommand proxytunnel -v -X -p proxy.dev:8118 -r spine.minithins.net:443 -d %h:%p -H "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Win32)\n"

On notera dans mon exemple que j’utilise le flag -X. Il permet de préciser que j’utilise une connection sécurisée ssl entre les deux proxy (entre mon proxy et le mod_proxy de mon apache). Il est possible que le proxy que vous utiliserez ne l’aime pas. Dans ce cas, n’hésitez pas à joeur avec -X, -e et -E).

Et si on restestait:

client# ssh spine-proxy
...
Tunneling to spine-proxy:2222 (destination)
Communication with remote proxy:
 -> CONNECT spine-proxy:2222 HTTP/1.0
 -> Proxy-Connection: Keep-Alive
 -> User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Win32)\n
 <- HTTP/1.1 502 Proxy Error
HTTP return code: 502 Proxy Error
...

What ?!
Dans les logs, on voit:

[error] [client 78.229.134.1] proxy: DNS lookup failure for: spine-proxy returned by spine-proxy:2222

Il n’aime pas trop que j’utilise « spine-proxy » comme alias. Solution, retirer le -d %h:%p (et le remplacer par le vrai host de la machine), ou changer l’alias. Après correction:

client# ssh -v spine
OpenSSH_5.5p1 Debian-4ubuntu5, OpenSSL 0.9.8o 01 Jun 2010
...
debug1: Executing proxy command: exec proxytunnel -v -X -p proxy.dev:8118 -r spine.minithins.net:443 -d spine:2222 -H "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Win32)\\n"
...
SSL local to remote proxy enabled
Local proxy proxy.dev resolves to 127.0.0.1
Connected to proxy.dev:8118 (local proxy)

Tunneling to spine.minithins.net:443 (remote proxy)
Communication with local proxy:
 -> CONNECT spine.minithins.net:443 HTTP/1.0
 -> Proxy-Connection: Keep-Alive
 -> User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Win32)\n
 <- HTTP/1.0 200 Connection established
 <- Proxy-Agent: Privoxy/3.0.16


Tunneling to spine:2222 (destination)
Communication with remote proxy:
 -> CONNECT spine:2222 HTTP/1.0
 -> Proxy-Connection: Keep-Alive
 -> User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Win32)\n
 <- HTTP/1.0 200 Connection Established
 <- Proxy-agent: Apache/2.2.16 (Debian)

Tunnel established.
debug1: Remote protocol version 2.0, remote software version OpenSSH_5.5p1 Debian-6
...
remote#

Et ça marche ! Rock’n'Roll ! :°)

Python: Chargement de fichiers sources en live

Pour BUTT j’ai voulu un mini système de plugins/modules afin de rajouter ou modifier du code sans redémarrer mon process. Et Python propose nativement cela grâce au module imp. On va utiliser spécifiquement la procédure load_source qui permet de charger un fichier source, de le parser, et de retourner son objet Module.

Illustrons. Voici le code du très simple module à importer:

#!/usr/bin/env python

class TestModule:
    def __init__(self):
        pass

    def myfunction(self):
        print "Calling myfunction"

Procédure de chargement du module:

#!/usr/bin/env python

import os, shutil
import imp

def load_module(module_name):
    print "Loading module %s" % module_name

    modfile = os.path.join(os.getcwd(), module_name + '.py')

    module = imp.load_source(module_name, modfile)

    print module
    # Returns <module 'test' from '/home/mycroft/imp/test.pyc'>

    print dir(module)
    # Permet de lister les differents attributs de l'objet importé.
    # Returns ['TestModule', '__builtins__', '__doc__', '__file__', '__name__', '__package__']

    for objname in dir(module):
        # Je veux specifiquement les modules suffixés "Module":
        if objname.endswith('Module'):
            modclass = getattr(module, objname)
            print modclass
            # Returns test.TestModule
            print dir(modlass)
            # Returns ['__doc__', '__init__', '__module__', 'myfunction']
            return modclass

    return None

Il ne reste plus qu’à montrer par l’exemple le fonctionnement et le reload d’un module dans le main:

if __name__ == '__main__':

    modclass = load_module('test')
    instance = modclass()
    instance.myfunction()

    # We move away test.py, and copy test_new.py to test.py.

    print

    shutil.move('test.py', 'test_old.py')
    shutil.move('test_new.py', 'test.py')

    modclass = load_module('test')
    instance = modclass()
    instance.myfunction()

    shutil.move('test.py', 'test_new.py')
    shutil.move('test_old.py', 'test.py')

Qui nous donnera:

$ ./modules.py
Loading module test
<module 'test' from '/home/mycroft/imp/test.py'>
['TestModule', '__builtins__', '__doc__', '__file__', '__name__', '__package__']
test.TestModule
['__doc__', '__init__', '__module__', 'myfunction']
Calling myfunction

Loading module test
<module 'test' from '/home/mycroft/imp/test.py'>
['TestModule', '__builtins__', '__doc__', '__file__', '__name__', '__package__']
test.TestModule
['__doc__', '__init__', '__module__', 'myfunction']
Calling (new) myfunction

Le code modifié (ici test.py a été remplacé par test_new.py, qui contient du code modifié) a bien été relu par python et est à présent utilisé.

Apache: Lire et découper le corps d’une requête POST dans un module Apache

Le code est très inspiré du chapitre « Reading the Request body » du livre Writing Apache Modules with Perl and C.

Pour expérimenter un peu les modules Apache (suite à un nouvel entretien d’embauche), je me suis mis en tête de coder un mini service via un module pour générer des nombres aléatoire.

Le service devra être très simple et sera exécuté lors de l’appel de l’url http://localhost/randomint, et l’on pourra l’appeler en POST en donnant, par exemple, l’argument count pour préciser le nombre d’entiers aléatoires que l’on désire.

Et finalement, l’étape la plus complexe aura été de reconstruire le body envoyé lors de la requête POST, et de le parser pour en sortir un tableau associatif (key/value).

Reconstruction du body

Cette fonction attend l’envoie du body par le client et reconstruit en un seul block toutes les données envoyées.

static int chunk_reader(request_rec *r, const char **rbuf)
{
    int ret;

    if(OK != (ret = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
        return ret;
    }

    if(ap_should_client_block(r)) {
        char argsbuffer[HUGE_STRING_LEN];
        int rsize, len_read, rpos=0;
        long length = r->remaining;

        *rbuf = apr_pcalloc(r->pool, length + 1);

        while(0 < (len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer)))) {
            if ((rpos + len_read) > length) {
                rsize = length - rpos;
            }
            else {
                rsize = len_read;
            }

            memcpy((char*)*rbuf + rpos, argsbuffer, rsize);
            rpos += rsize;
        }
    }

    return ret;
}

Création du tableau associatif

Avec le block recomposé, on fait ensuite appel aux fonctions ap_getword ap_getword et aux fonctions apr_tables de apr afin de découper et stocker les différentes valeurs envoyées:

static int read_post(request_rec *r, apr_table_t **tab)
{
    const char *data;
    const char *key, *val, *type;
    int ret = OK;

    if(r->method_number != M_POST) {
        return ret;
    }

    // On verifie que le content type soit bien application/x-www-form-urlencoded
    type = apr_table_get(r->headers_in, "Content-Type");

    if(strcasecmp(type, DEFAULT_ENCTYPE) != 0) {
        return DECLINED;
    }

    if((ret = chunk_reader(r, &data)) != OK) {
       return ret;
    }

    // Creation ou RAZ de la apr_table_t
    if(*tab) {
        apr_table_clear(*tab);
    }
    else {
        *tab = apr_table_make(r->pool, 8);
    }

    // Tant qu'il y a des datas...
    while(*data && (val = ap_getword(r->pool, &data, '&'))) {
        key = ap_getword(r->pool, &val, '=');
        ap_unescape_url((char*)key);
        ap_unescape_url((char*)val);
        apr_table_merge(*tab, key, val);
    }

    return OK;
}

Récupérer la bonne valeur

Une fois qu’on a le tableau associatif, il nous faut une fonction pour récupérer la valeur voulue:

int get_value(request_rec *r, const char *name)
{
    apr_table_t *post_values = NULL;
    apr_array_header_t *arr;
    apr_table_entry_t *elt;

    int i, ret, value;

    if(OK != (ret = read_post(r, &post_values))) {
        return -1;
    }

    if(NULL == post_values) {
        return -1;
    }

    arr = apr_table_elts(post_values);
    elt = (apr_table_entry_t*)arr->elts;

    for(i = 0; i < arr->nelts; ++i) {
        if(strcmp(elt[i].key, name) == 0
        && LONG_MIN != (value = strtol(elt[i].val, NULL, 10))) {
            return value;
        }
    }

    return -1;
}

Revenons au code du module lui-même…

Il ne reste plus qu’à coder le handler de génération, et rajouter les structures nécessaires pour le module:

static int mod_randomint_method_handler (request_rec *r)
{
    int ret = OK, i, count = 8, new_count;
    unsigned int random_value;

    new_count = get_value(r, "count");

    if(new_count > 0 && new_count < MAX_COUNT) {
        count = new_count;
    }

    ap_set_content_type(r, "text/plain");

    int random_fd = open("/dev/urandom", O_RDONLY);
    for(i = 0; i < count; i ++) {
        read(random_fd, (void*)&random_value, sizeof(random_value));
        ap_rprintf(r, "%u\n", random_value);
    }
    close(random_fd);

    // fprintf(stderr, "Count: %d\n", count);
    // fflush(stderr);

    return ret;
}

static void mod_randomint_register_hooks (apr_pool_t *p)
{
    ap_hook_handler(mod_randomint_method_handler, NULL, NULL,APR_HOOK_LAST);
}

module AP_MODULE_DECLARE_DATA randomint_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    mod_randomint_register_hooks,    /* callback for registering hooks */
}

Il ne reste plus qu’à compiler le code …:

apxs2 -c -a -i mod_randomint.c

… et le configurer dans httpd.conf:

LoadModule randomint_module /usr/lib/apache2/modules/mod_randomint.so

    <Location /randomint>
      SetHandler randomint-handler
    </Location>

Et après un redémarrage d’apache:

$ curl http://localhost/randomint
    4162010288
    1220025110
    2641785880

On pourra voir le résultat dans mes snippets et le télécharger dans mes projets directement.

Mise à jour:

J’ai oublié de filtrer par handler dans le code. On incluera donc dans mod_randomint_method_handler:

    if(strcmp(r->handler, "randomint-handler")) {
        return DECLINED;
    }

Recréer un une image ISO en image UDF (pour les DVDs)

J’ai téléchargé récemment une image ISO contenant des clips et un menu DVD. Celle ci n’était pas lisible par mplayer:

$ mplayer -dvd-device image.iso dvd://
libdvdnav:DVDOpenFileUDF:UDFFindFile /VIDEO_TS/VIDEO_TS.IFO failed
libdvdnav:DVDOpenFileUDF:UDFFindFile /VIDEO_TS/VIDEO_TS.BUP failed
libdvdread: Can't open file VIDEO_TS.IFO.
Can'
t open VMG info!
No stream found to handle url dvd://

En montant l’image, on peut quand même bien vérifier la présence des fichiers prétendus « manquants »:

# mkdir image ; mount -o loop image.iso image/
# ls -l image/VIDEO_TS/VIDEO_TS.{BUP,IFO}
-r-xr-xr-x 1 root root 12288 Apr  8 00:36 image/VIDEO_TS/VIDEO_TS.BUP
-r-xr-xr-x 1 root root 12288 Apr  8 00:36 image/VIDEO_TS/VIDEO_TS.IFO

D’où venait le problème ? Il vient du fait que l’image soit une image ISO 9660 et non une image UDF.

# file image.iso
image.iso: ISO 9660 CD-ROM filesystem data 'blabla'

Solution: Reconstruire une image UDF. Pour cela, rien de plus facile qu’un coup de mkisofs (http://freshmeat.net/projects/mkisofs/):

# mkisofs -r -udf -o image.udf image/
[...]
# file image.udf
image.udf: UDF filesystem data (version 1.5) 'CDROM'

(Note: image/ a été monté juste avant. Ce répertoire contient un répertoire VIDEO_TS et un AUDIO_TS)

Il ne reste plus qu’à profiter de son image dvd toute fraiche:

$ mplayer -dvd-device image.udf dvdnav://
[...]
Playing dvdnav://.
libdvdnav: Using dvdnav version 4.1.4
libdvdread: Using libdvdcss version 1.2.10 for DVD access
libdvdnav: DVD Title: CDROM
libdvdnav: DVD Serial Number: 4C05646C00000000
libdvdnav: DVD Title (Alternative):
libdvdnav: Unable to find map file '/root/.dvdnav/CDROM.map'
[...]