Le *namespace* `IPC` {#ipc-ns} ------------------- ### Introduction L'espace de noms `IPC`, introduit dans Linux 2.6.19, isole les objets de communication inter-processus (IPC) System V et les files de messages POSIX. Les objets isolés incluent : - Les files de messages System V (`msgget`, `msgsnd`, `msgrcv`) - Les sémaphores System V (`semget`, `semop`) - Les segments de mémoire partagée System V (`shmget`, `shmat`) - Les files de messages POSIX (`mq_open`, `mq_send`, `mq_receive`) Une fois dans un *namespace* IPC différent, un processus ne peut communiquer qu'avec les processus du même *namespace* via ces mécanismes IPC. ### Pourquoi isoler les IPC ? Sans isolation IPC, des processus dans différents conteneurs pourraient : - Communiquer entre eux via des files de messages partagées - Accéder à la mémoire partagée d'autres conteneurs - Interférer avec les sémaphores d'autres applications L'isolation IPC garantit que chaque conteneur dispose de son propre ensemble d'objets IPC, renforçant ainsi la sécurité et l'isolation. ### Utilisation pratique #### Observer les objets IPC du système\ La commande `ipcs` permet de lister tous les objets IPC du système :
```bash 42sh$ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 32768 alice 600 524288 2 dest ------ Semaphore Arrays -------- key semid owner perms nsems 0x12345678 0 bob 666 1 ```
Cette commande nous montre tous les objets IPC visibles dans notre *namespace* actuel. #### Créer une file de messages System V\ Créons d'abord une file de messages dans notre *namespace* initial :
```c // ipc_create.c #include #include #include #include int main(void) { key_t key; int msgid; // Créer une clé unique key = ftok("/tmp", 'A'); if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); } // Créer une file de messages msgid = msgget(key, 0666 | IPC_CREAT); if (msgid == -1) { perror("msgget"); exit(EXIT_FAILURE); } printf("File de messages créée avec l'ID : %d\n", msgid); printf("Clé : 0x%08x\n", key); return 0; } ```
::::: {.code} Compilation et exécution :
```bash 42sh$ gcc -o ipc_create ipc_create.c 42sh$ touch /tmp/ipc_key_file 42sh$ ./ipc_create File de messages créée avec l'ID : 0 Clé : 0x41000001 42sh$ ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages 0x41000001 0 alice 666 0 0 ```
::::: #### S'isoler dans un nouveau *namespace* IPC\ Maintenant, créons un nouveau *namespace* IPC :
```bash 42sh$ unshare --ipc /bin/bash inipcns$ ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages ```
Notre nouveau *namespace* ne contient aucun objet IPC ! La file de messages créée précédemment n'est pas visible ici. Si nous créons une nouvelle file de messages dans ce *namespace* :
```bash inipcns$ ./ipc_create File de messages créée avec l'ID : 0 Clé : 0x41000001 inipcns$ ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages 0x41000001 0 alice 666 0 0 ```
Cette nouvelle file a le même ID (0) et la même clé que celle créée dans le *namespace* initial, mais il s'agit d'un objet complètement distinct. #### Communication entre processus dans le même *namespace*\ Créons un exemple de communication par file de messages :
```c // ipc_send.c #include #include #include #include #include struct message { long mtype; char mtext[100]; }; int main(void) { key_t key; int msgid; struct message msg; key = ftok("/tmp", 'A'); msgid = msgget(key, 0666 | IPC_CREAT); msg.mtype = 1; snprintf(msg.mtext, sizeof(msg.mtext), "Hello from PID %d", getpid()); if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) { perror("msgsnd"); exit(EXIT_FAILURE); } printf("Message envoyé : %s\n", msg.mtext); return 0; } ```
```c // ipc_receive.c #include #include #include #include struct message { long mtype; char mtext[100]; }; int main(void) { key_t key; int msgid; struct message msg; key = ftok("/tmp", 'A'); msgid = msgget(key, 0666 | IPC_CREAT); if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) { perror("msgrcv"); exit(EXIT_FAILURE); } printf("Message reçu : %s\n", msg.mtext); return 0; } ```
::::: {.code} Compilation :
```bash 42sh$ gcc -o ipc_send ipc_send.c 42sh$ gcc -o ipc_receive ipc_receive.c ```
Test dans le même *namespace* :
```bash # Terminal 1 42sh$ ./ipc_receive # Attend un message... # Terminal 2 42sh$ ./ipc_send Message envoyé : Hello from PID 12345 # Terminal 1 reçoit : Message reçu : Hello from PID 12345 ```
::::: #### Isolation entre *namespaces*\ Maintenant, testons avec deux *namespaces* IPC différents :
```bash # Terminal 1 - namespace IPC A 42sh$ unshare --ipc /bin/bash ipcns-A$ ./ipc_receive # Attend un message... # Terminal 2 - namespace IPC B (différent) 42sh$ unshare --ipc /bin/bash ipcns-B$ ./ipc_send Message envoyé : Hello from PID 23456 # Terminal 1 ne reçoit RIEN car les namespaces sont différents ```
Les deux processus utilisent la même clé IPC, mais comme ils sont dans des *namespaces* différents, ils ne peuvent pas communiquer. ### Exemple avec la mémoire partagée Voici un exemple utilisant la mémoire partagée System V :
```c // shm_demo.c #include #include #include #include #include #include int main(void) { key_t key; int shmid; char *data; // Créer une clé key = ftok("/tmp", 'S'); // Créer un segment de mémoire partagée de 1024 octets shmid = shmget(key, 1024, 0644 | IPC_CREAT); if (shmid == -1) { perror("shmget"); exit(EXIT_FAILURE); } // Attacher le segment data = shmat(shmid, NULL, 0); if (data == (char *)(-1)) { perror("shmat"); exit(EXIT_FAILURE); } printf("Segment de mémoire partagée attaché\n"); printf("Écriture dans la mémoire partagée...\n"); sprintf(data, "Hello from PID %d in namespace", getpid()); printf("Contenu : %s\n", data); printf("Appuyez sur Entrée pour détacher...\n"); getchar(); // Détacher shmdt(data); printf("Voulez-vous supprimer le segment ? (o/n) : "); char choice = getchar(); if (choice == 'o' || choice == 'O') { shmctl(shmid, IPC_RMID, NULL); printf("Segment supprimé\n"); } return 0; } ```
::::: {.code} Compilation et test :
```bash 42sh$ gcc -o shm_demo shm_demo.c # Dans le namespace initial 42sh$ ./shm_demo & Segment de mémoire partagée attaché Écriture dans la mémoire partagée... Contenu : Hello from PID 12345 in namespace 42sh$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x53000001 0 alice 644 1024 1 # Dans un nouveau namespace IPC 42sh$ unshare --ipc /bin/bash inipcns$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status # Aucun segment visible ! ```
::::: ### Files de messages POSIX Le *namespace* IPC isole également les files de messages POSIX (qui sont différentes des files System V) :
```c // mqueue_demo.c #include #include #include #include #include int main(void) { mqd_t mq; struct mq_attr attr; char buffer[1024]; // Configurer les attributs de la file attr.mq_flags = 0; attr.mq_maxmsg = 10; attr.mq_msgsize = 1024; attr.mq_curmsgs = 0; // Créer ou ouvrir une file de messages POSIX mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0644, &attr); if (mq == (mqd_t)-1) { perror("mq_open"); exit(EXIT_FAILURE); } printf("File de messages POSIX créée : /myqueue\n"); // Envoyer un message sprintf(buffer, "Hello from PID %d", getpid()); if (mq_send(mq, buffer, strlen(buffer) + 1, 0) == -1) { perror("mq_send"); exit(EXIT_FAILURE); } printf("Message envoyé : %s\n", buffer); // Recevoir le message if (mq_receive(mq, buffer, 1024, NULL) == -1) { perror("mq_receive"); exit(EXIT_FAILURE); } printf("Message reçu : %s\n", buffer); mq_close(mq); mq_unlink("/myqueue"); return 0; } ```
::::: {.code} Pour compiler (nécessite la bibliothèque `librt`) :
```bash 42sh$ gcc -o mqueue_demo mqueue_demo.c -lrt 42sh$ ./mqueue_demo File de messages POSIX créée : /myqueue Message envoyé : Hello from PID 12345 Message reçu : Hello from PID 12345 ```
Les files de messages POSIX peuvent être listées dans `/dev/mqueue/` :
```bash 42sh$ ls -l /dev/mqueue/ total 0 -rw-r--r-- 1 alice alice 80 Nov 15 10:30 myqueue ```
Dans un nouveau *namespace* IPC, ce répertoire sera vide. ::::: ### Combinaison avec d'autres *namespaces* En pratique, le *namespace* IPC est souvent combiné avec d'autres *namespaces* pour une isolation complète :
```bash 42sh# unshare --ipc --pid --mount --fork --mount-proc /bin/bash incontainer# # Conteneur isolé avec IPC, PID et mount namespaces ```
### Nettoyage des objets IPC Les objets IPC persistent généralement après la terminaison des processus qui les ont créés. Pour les nettoyer :
```bash # Supprimer une file de messages 42sh$ ipcrm -q # Supprimer un segment de mémoire partagée 42sh$ ipcrm -m # Supprimer un sémaphore 42sh$ ipcrm -s # Ou supprimer par clé 42sh$ ipcrm -Q 0x41000001 ```
Lorsqu'un *namespace* IPC est détruit (tous les processus terminés), tous ses objets IPC sont automatiquement nettoyés. ### Aller plus loin {-} Pour plus d'informations : - `ipc_namespaces(7)` - page de manuel - `ipcs(1)` - lister les objets IPC - `ipcrm(1)` - supprimer les objets IPC - `svipc(7)` - aperçu de System V IPC - `mq_overview(7)` - aperçu des files de messages POSIX