OpenBSD

NSD pour remplacer BIND

date
17 / 11 / 2010
comments
0

Je loue une machine virtuelle chez lost-oasis à (12€ par mois, 256Mo de RAM) pour faire du monitoring nagios, du mx/dns secondaire et du mirror http pour mes autres machines dans mon appart. Elle tourne sur Debian (en paravirtualisation kvm, virtio etc), avec de l'openvz par dessus. 256Mo ça fait pas beaucoup, et en browsant sur mon top, j'ai remarqué que c'était bind qui prenait le plus de mémoire (loin devant postgresql et php-cgi).

Je me met donc à la recherche d'un serveur dns qui serait plus léger que bind et qui fasse aussi bien dns primaire et secondaire pour mes 5 petites zones et je me souviens avoir vu passer une news à propos de l'intégration récente de nsd dans OpenBSD-Current. Ni une ni deux je dégaine mon apt-get install nsd3 et après quelques jours d'utilisation j'en suis très content.

Ce qu'il faut savoir à propos de nsd :

  • C'est un serveur authoritative only (contrairement à bind qui fait aussi cache et récursion)
  • Il est utilisé par quelques uns des root dns servers (on peut donc lui louer une certaine robustesse)
  • Il sait lire les même fichiers de zones que bind
  • La configuration est extrêmement simple
  • Il est bien plus léger en RAM que bind même configuré pour faire la même chose.
  • Il fonctionne très bien avec bind en master (j'ai pas testé dans l'autre sens)

Que du bon donc, laisser moi vous montrer un bout de config comme c'est simple :

server:
    hide-version: yes

# Une config master avec deux slaves 
zone:
    name: "philpep.org"
    zonefile: "master/philpep.org"

    notify: 217.70.177.40 NOKEY
    provide-xfr: 217.70.177.40 NOKEY

    notify: 212.85.155.53 NOKEY
    provide-xfr: 212.85.155.53 NOKEY

# Une config slave avec un master
zone:
    name: "philpep.org"
    zonefile: "slave/philpep.org"

    allow-notify: 82.229.137.130 NOKEY
    request-xfr: AXFR 82.229.137.130 NOKEY

# Apparemment nsd master ne gère que les transferts de zone AXFR (et pas IXFR)
# Ça n'a pas d'incidence l'un est testé puis l'autre si ça marche pas. Mais vous pouvez éviter
# un message de log en mettant AXFR dans request-xfr dans le cas d'un nsd master et slave.

Les zones sont strictement les mêmes que celles de bind.

Bien sûr il est possible de faire des config bien plus velues avec des clés pour faire des zones dynamiques, du chroot etc, voyez nsd.conf(5)

Il y a aussi un utilitaire bien convi pour controler le serveur : nsdc(8). Avant de démarrer le serveur pour la première fois, il faut compiler les zones avec nsdc rebuild.

J'ai trouvé nsd tellement pratique que je l'ai aussi installé en master sur mon serveur FreeBSD (dns/nsd), bind sert toujours pour le cache,récursion et mes zones en local, mais je vais certainement le remplacer par un djbdns ou un dnsmasq

Le script rc de nsd sur FreeBSD /usr/local/etc/rc.d/nsd est pas terrible, utilisez plutôt nsdc ou attendez que mon patch soit accepté :-)

Ma conf NanoBSD

Je suis l'heureux possesseur d'une alix 2D3, un temps elle a tournée sous OpenBSD mais quand est venue la carte wifi non supportée j'ai du changer d'OS pour FreeBSD et les drivers madwifi. Un jour j'en ai eu marre de me battre avec mount -uw / et les devices md(4) et j'y ais mis une pfsense, au début l'interface web c'est convi, mais vite on se sent limité, et ne pas éditer mon pf.cont(5) avec vim me rendait malade.

Puis j'apprends qu'il existe un script dans les sources FreeBSD qui permet de faire une FreeBSD pour l'embarqué (ie, petite taille, lecture seule + ram disk). C'est marqué dans le titre, il s'agit de nanobsd(8).

C'est très bien foutu et on est assez émerveillé quand on connait pas les techniques de l'embarqué comme moi.

En gros vous faites un fichier de config en shell, /usr/src/tools/tools/nanobsd/nanobsd.sh se charge de vous compiler le world et le kernel et mettre tout ça dans une image disque.

Il y a beaucoup de paramètres customisables, une simple lecture du script vous les donnera tous. Nanobsd génère 2 images, une image disque complete (.disk.full) et une image avec seulement une partition (.disk.image). L'image complète comporte 3 partitions, les deux premières sont identiques en tailles et contiennent les mêmes données que .disk.image, lors de la première installation vous utilisez .disk.full, et pour mettre à jour vous utilisez _.disk.image à écrire sur la partition que vous n'utilisez pas actuellement (comme ça pas besoin de sortir la carte flash pour mettre à jour).

Nanobsd monte une des deux partitions sur / (en read-only), et deux ram disk pour /etc et /var (/tmp est linké avec /var/tmp, /usr/local/etc sur /etc/local). Pour rendre les changements sur /etc persistants, on utilise la troisième partition qui se monte sur /cfg, tout les fichiers de /cfg sont écrits sur /etc au boot.

Il y a un script simple pour comparer /etc et /cfg pour sauvegarder les changements.

% mount /cfg
% touch /cfg/rc.conf # si le fichier n'existe pas dans /cfg il n'est pas copié
% umount /cfg
% sh /root/save_cfg
/etc/rc.conf --> /cfg/rc.conf
%

Voyez le tgz avec ma config, vous y trouverez la config kernel, la config nanobsd, et les fichiers que j'ai mis dans /usr/src/tools/tools/nanobsd/Files/

Avec en particulier dnsmasq.conf qui bloque au niveau dns les publicités (avec une liste updatée tous les jours).

Mon alix sert donc de routeur, serveur dns/dhcp/impression. Sendmail relaye vers mon serveur interne, et syslog envoie tout à mon serveur.

% cd /usr/src/tools/tools/nanobsd/
% cp ~/solo/alix.cfg .
% cp -r ~/solo/Files/* Files/
% cp ~/solo/SOLO /usr/src/sys/i386/conf/
% sh nanobsd.sh -c alix.cfg
% wait
% qemu -hda /usr/obj/nanobsd.solo/_.disk.full -m 512 -no-acpi -nographic -cpu pentium
# Hmm quelque chose ne va pas, mais j'ai pas envie de rebuild world et/ou kernel
% sh nanobsd.sh -h

Mon script utilise des packages préconstruits, si vous voulez les compiler vous même il y a une fonction pour ça dans nanobsd.sh

Pour finir (ou commencer), voici la doc nanobsd

Voilà, ma config est certainement loin d'être parfaite niveau taille système mais ma carte flash fait 4G (donc 2G utilisable pour le système) et c'est largement suffisant pour faire tourner une FreeBSD "pas trop dépouillée".

Pour mettre à jours (j'ai gzip mon image avant) en supposant que le système tourne sur la partition 1:

% ssh phil@aldo cat /usr/obj/nanobsd.solo/_.disk.image.gz | zcat | sh updatep2
% reboot

J'ai modifié updatep{1|2} en ajoutant gpart set -a active -i PARTITION_INDEX ad0 en raison d'un problème que vous aurez peut être (la nouvelle partition est pas marquée active...)

Si le sujet vous intéresse, il y a un projet linux embarqué sympa qui s'appelle buildroot où là on peut carrément construire sa chaîne de compilation croisée (arm, mips etc), et construire un système embarqué très personalisé. Dommage qu'il n'existe pas un tel projet basé sur le kernel BSD (un jour peut être...)

Astuce pf du jour

date
23 / 2 / 2010
comments
0

J'ai la chance d'avoir une bibliothèque avec un rayon informatique bien rempli dans mon Université, dernièrement j'y ai emprunté ce bouquin. Là dedans j'y ai vu une syntaxe pour décrire le réseau local que j'avais raté quand j'ai lu la doc :

ext_if = "rl0"
# Au lieu de
localnet = "{ 192.168.0.0/24, 2a01:e35:2e58:9820::/64 }"
# On peut mettre
localnet = $ext_if:network
# Ou encore
localnet = rl0:network

Pf va lui même calculer le réseau local à l'aide de l'IP du netmask et du prefixe.

Attention tout de même si vous avez plusieurs IP (v4 et v6) sur l'interface, ça peut produire des règles redondantes, en cas de doute :

pfctl -sr

EDIT (27/02/10) : En fait quand il y a des alias sur l'IP il va les évaluer mais pour ne spécifier que l'adresses (sans les alias) on peut mettre $ext_if:network:0

IPV6 step 1

date
21 / 2 / 2010
comments
1

Je viens de passer tout (ou presque) mes services en IPV6 (en gardant IPV4 of course) :

% host blog.philpep.org
blog.philpep.org is an alias for lenine.philpep.org.
lenine.philpep.org has address 82.229.137.130
lenine.philpep.org has IPv6 address 2a01:e35:2e58:9820::5

Donc l'idée c'est de faire un récit de mon passage étant donné le nombre de modifications que j'ai du faire sur la configurations des machines, des softs et des pare feu. Je vais faire tout ça en 2 ou 3 billets.

Mon FAI (free) me fournis un préfixe IPV6 2a01:e35:2e58:9820::/64, et ma topologie réseau permet sans se casser la tête de faire fonctionner tout ça. Le réseau est composé de 3 machines (+ quelques intrus en wifi), un routeur OpenBSD, un serveur FreeBSD avec 4 jails et mon laptop FreeBSD. Le mode routeur est activé sur la freebox (sans DHCP) et elle installe un 192.168.0.0/24 sur lequel sont toutes les machines (jails y compris) sont configurés, ainsi toutes les machines sont reliés physiquement sur la freebox ce qui permet d'éviter les problèmes de neighbor solicitation si seul mon routeur serait physiquement relié à la freebox (on peut passer outre avec ndp(8))

En ipv4 :

+-------------+      +----------------+       +---------+
| SRV + jails |<-----|     Routeur    |<------| Freebox |
+-------------+      +----------------+       +---------+
                      Filtrage/Routage

    En IPV4 tout le trafic passe par le routeur qui dispatche sur les jails.

Et en ipv6 :

+-------------+
| SRV + jails |<------+
+-------------+       |
                      |    +---------+
                      +----| Freebox |
                      |    +---------+
+------------+        |
|  Routeur   |<-------+
+------------+

En IPV6 le traffic est distribué par la freebox,
le routeur ne sert plus (à part les services qui
sont dessus).

La configuration ipv6 des machines sous FreeBSD :

# /etc/rc.conf
ipv6_enable="YES"
ipv6_ifconfig_rl0="2a01:e35:2E58:9820::2 prefixlen 64"
ipv6_defaultrouter="2a01:e35:2E58:9820::1"

# ... La config jail
jail_lenine_ip="192.168.0.5,2a01:e35:2E58:9820::5"
# ...

Un petit reboot (où un /etc/rc.d/pleins_de_trucs restart)

Sur OpenBSD :

# /etc/hostname.vr0
inet 192.168.0.1 255.255.255/0
inet6 2a01:e35:2E58:9820::3 64
# /etc/mygate (freebox)
192.168.0.254
2a01:e35:2E58:9820::1

Après il faut modifier votre pf.conf en supprimant inet de vos règles de filtrage comme ça on sous entend inet et inet6, concernant icmp c'est plus délicat parce qu'il y a deux protocoles.

# /etc/pf.conf
pass in inet proto icmp
pass in inet6 proto icmp6

Ensuite ça devrait tourner :

% ping6 -c 2 www.google.com
PING6(56=40+8+8 bytes) 2a01:e35:2e58:9820::2 --> 2a00:1450:8002::67
16 bytes from 2a00:1450:8002::67, icmp_seq=0 hlim=54 time=49.177 ms
16 bytes from 2a00:1450:8002::67, icmp_seq=1 hlim=54 time=53.867 ms

--- www.l.google.com ping6 statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 49.177/51.522/53.867/2.345 ms

% traceroute6 www.google.com
traceroute6: Warning: www.l.google.com has multiple addresses; using 2a00:1450:8002::6a
traceroute6 to www.l.google.com (2a00:1450:8002::6a) from 2a01:e35:2e58:9820::2, 64 hops max, 12 byte packets
 1  2a01:e35:2e58:9820::1  0.589 ms  0.530 ms  0.480 ms
 2  6to4-b8-e3.intf.routers.proxad.net  35.128 ms  33.359 ms  38.152 ms
....
# HO LE VILAIN TUNEL ! :>

Vous voilà en IPV6, la première chose à faire c'est reconfigurer votre parre feu, normalement vous n'aurez pas à doubler chaque lignes, enlevez juste inet là où il faut et ça passe. Les seules lignes à doubler sont les redirections (rdr), parce qu'il faut envoyer sur une adresse IPV4 ou sur une adresse IPV6.

Dans le prochain billet j'expliquerais comment passer petit à petit ses softs sur IPV6.

Jouons avec kvm

date
13 / 2 / 2010
comments
1

J'ai codé un petit programme tout simple qui fait clignoter les leds de mon routeur dès qu'un service est par terre. Ma méthode (qui ne doit pas être la meilleure mais je m'en fout un peu) consiste à regarder régulièrement la liste des processus et détecter l'arrêt d'un service quand le processus associé n'existe plus. J'ai commencé avec un script shell, puis étant assez mauvais pour tout ce qui ressemble de près ou de loin à du script j'ai décidé de le faire en C (langage d'excellence n'est ce pas...?).

Je me suis volontairement compliqué la tache en utilisant des fonctions très proches du système, ainsi s'il est possible d'obtenir une liste de processus via ps(1) avec un popen(3) super crade ou encore sysctl(3) j'ai voulu utiliser kvm(3) kernel memory interface qui est en programmation système la méthode la plus appropriée (d'ailleurs 'ps' est codé avec ça), c'est juste une interface qui va nous permettre d'accéder au données du kernel (une copie bien entendu), ici la liste des processus en cours.

Le hic c'est que si les fonctions de kvm sont très standard l'implémentation est très différente suivant les systèmes, comme à peu près toute implémentation, l'important c'est que l'interface soit standard. Ainsi à la lecture de la structure qui décrit les processus kinfo_proc de sys/user.h on se dit chouette pleins d'info à disposition, sauf que sur OpenBSD c'est pas du tout la même forme (ni le même fichier d'ailleurs). D'où l'importance d'une interface riche et c'est ce que nous promet kvm.

Assez causé passons au code (je n'explique pas ou peu le code en dehors des fonctions kvm)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <libgen.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <kvm.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <sys/user.h>

void
start_leds(void)
{
    /* Ici on execute /usr/sbin/gioctl -q gpio0 led3 (on|off)
     * suivant la parité du conteur statique 'i'
     * 60 fois avec une pause d'une seconde. */
    static int i = 1;
    switch(fork())
    {
        case -1:
            err(1, "fork");
            break;
        case 0:
            execl("/usr/sbin/gpioctl", "gpioctl", "-q", "gpio0",
                    "led3", (i%2) ? "on" : "off", (char*)NULL);
            err(1, "execl");
            break;
        default:
            wait(NULL);
            break;
    }
    sleep(1);
    if (i++ <= 60)
        return start_leds();
    return;
}

int
main(int argc, char *argv[])
{
    kvm_t *kd;
    char berr[_POSIX2_LINE_MAX];
    struct kinfo_proc *p, *procs;
    int n, j, k, found = 0;
    char **pargv;

    if (argc < 2)
        errx(1, "Usage check_proc procs ...");

    /* On accède aux données du kernel qui tourne actuellement (NULL) */
    if (!(kd = kvm_open(NULL, NULL, NULL, O_RDONLY, berr)))
        errx(1, "%s", berr);

    /* On demande la liste des processus, kinfo_proc est décrite
     * dans sys/sysctl.h sur OpenBSD et sys/user.h sur FreeBSD.
     * Il nous la donne dans un tableau continu de pointeurs de
     * taille qu'on récupère dans 'n'. */
    if (!(procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &n)))
        err(1, "kvm_getprocs: %s", kvm_geterr(kd));

    for (k = 1; k < argc; k++)
    {
        for (j = 0, p = procs; j < n; j++, p++)
        {
            /* On accède à la liste des arguments du programme qui a
             * généré le processus courant. */
            pargv = kvm_getargv(kd, p, 0);
            if (pargv && pargv[0] &&
                    strstr(pargv[0], argv[k]))
                {
                    found++;
                    break;
                }
        }
    }

    if (-1 == kvm_close(kd))
        err(1, "kvm_close");

    if (found != argc-1)
        start_leds();

    return 0;
}

Et l'exécution, on veut tester si un processus du nom de 'dhcpd' tourne sur le système, si ce n'est pas le cas on lance le clignotement de la led pendant une minute :

# gcc -o check_proc check_proc.c -lkvm
# ./check_proc dhcpd

Le mien s'exécute dans un cron toute les minutes :

* * * * * /root/bin/check_proc named dhcpd adsuck ntpd