dimanche 14 octobre 2012

Serveur FTP simple et limitation de débit

[Les configurations sont à la fin]
Bon alors voila, sur mon OpenG@te Pyxis (qui tourne sous Amahi, une Fedora 14 customisée pour en faire facilement une box centrale à la maison ; trop même dès fois puisqu'elle remplace le serveur DHCP par défaut), y'a un disque dur qui contient 2To de données que j'ai envie de rendre accessible à quelques personnes en lecture seule sans me prendre la tête.
J'avais d'abord pensé proposer un accès SFTP/SCP, mais c'est pas forcément le plus simple (il faut créer des utilisateurs et s'assurer que ceux-ci n'ont accès qu'au partage) et surtout, je n'ai pas trouvé le moyen de brider le débit pour ne pas ralentir la connexion au quotidien (parce que quand l'upload est a fond, la navigation devient chiante).

Je me suis donc tourné vers le bon vieux FTP, dans un premier temps en clair, puis ensuite en chiffré, à l'aide du simple mais efficace et sécurisé VsFTPd. Il propose une gestion des utilisateurs anonyme qui convient très bien à mon usage : un utilisateur commun avec une liste de mots de passes valides (-> 1/personne) dans un fichier texte : c'est facile à maintenir.
Pour le gestion des utilisateurs, c'est bon, il reste la gestion de la bande passante ; j'avais dans un premier temps pensé utiliser la directive "anon_max_rate" pour brider les anonymes .. le problème est qu'elle s'applique par connexion -> cela oblige à limiter l'accès au serveur à 1 connexion/client et 1 client. En plus, pour proposer le SSL explicite et implicite en IPv4 et IPv6, ce sont 4 instances du daemon qui tournent .. chacune ayant sa limitation .. Pas bon toussa.

Je me dis qu'il y a bien un autre serveur qui proposera cette option, je retourne donc voir mes autres options :
  • ProFTPd : configuration Apache-style qui me semble une usine à gaz, on oublie
  • glFTPd : semble presque abandonné, la gestion des utilisateurs ne me convient pas et semble prévue pour gérer du warez
  • Pure-FTPd : semble sympa, même si en mode virtuel il faut créer des utilisateurs dans une BDD spécifique, je vois une option "anonymousbandwidth" sympa .. seulement en jetant un œil dans la FAQ, on y trouve
    * Global bandwidth limitation.
    -> How do I limit the *total* bandwidth for FTP?
    Pure-FTPd can limit bandwidth usage of every session. But limiting the total bandwidth is intentionally not implemented, because most operating systems already have very efficient algorithms to handle bandwidth throttling.
    Comme pour VsFTPd, la limitation est par session, et avec cette gestion des utilisateurs, c'est encore moins bon.
Visiblement, un serveur simple avec une limitation globale de la BP ça n'existe pas, je décide donc de garder VsFTPD mais en suivant la méthode proposée par Pure-FTPd à base de TC (traffic control) pour limiter la BP.
    Après une après-midi pour comprendre comment fonctionnent les QDisc, Class, Filter et pour ne pas casser le réglage qui semble exister par défaut (qdisc pfifo_fast 0: dev eth0 root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1), j'ai enfin une limitation du débit qui fonctionne !

    En résumé, sur mon interface eth0 j'ai une qdisc htb qui me permet d'avoir 2 classes htb filles qui permettent une gestion du débit, l'une est à 100MBit/s (i.e. sans limite), par défaut, l'autre est bridée à 90kbps et accueillera les paquets selectionnés. Sous ces classes, je remet une gestion des priorités (dépendant du champ TOS) à l'aide de prio.
                  1:0      htb envoi vers 1:1 par défaut
                 /   \
                /     \
              1:1     1:2  htb rate 100mbit et rate 90kbps
               |       |
              10:     20:  
             (prio)  (prio)
    
    Si j'ai compris, la racine de cet arbre est à la fois l'entrée et la sortie des paquets : le noyau les envoi puis ils sont rangés et ressortis des files par les différents moteurs selon les paramétrages (1:, 1:2, 20:, 1:2: 1:0 par exemple).

    Ensuite un filtre vient placer les paquets marqués par iptables dans la classe limitée ; ainsi, tout les paquets concernant le FTP (e/issl, ipv4/6) se retrouvent dans cette classe et partagent le même débit.

    Bref, fini les explications, voici les confs :
    [root@Pyxis vsftpd]# ls conf*
    conf_ipv4_essl.conf  conf_ipv4_issl.conf  conf_ipv6_essl.conf  conf_ipv6_issl.conf
    [root@Pyxis vsftpd]# cat conf_ipv4_essl.conf
    # Hey, Listen !
    listen=YES
    listen_ipv6=NO
    implicit_ssl=NO
    listen_port=21
    #pasv_addr_resolve=no
    pasv_address=78.232.184.204 #vela.lxdr.net
    max_per_ip=4
    max_clients=10
    # Default
    pam_service_name=vsftpd
    userlist_enable=YES
    tcp_wrappers=YES
    #
    anonymous_enable=YES
    local_enable=YES
    local_umask=022
    dirmessage_enable=YES
    connect_from_port_20=YES
    # Changes
    banner_file=/etc/vsftpd/banner.txt
    setproctitle_enable=YES
    write_enable=NO
    xferlog_enable=YES
    dual_log_enable=YES
    pasv_min_port=9800
    pasv_max_port=9850
    # Share
    secure_email_list_enable=YES
    #email_password_file=/etc/vsftpd/email_passwords
    anon_root=/media/2t/Medias/
    #anon_max_rate=87040
    # SSL
    ssl_enable=YES
    #debug_ssl=YES
    require_ssl_reuse=NO
    #ssl_request_cert=NO
    ssl_ciphers=HIGH
    rsa_cert_file=/etc/vsftpd/wildcard.13x.fr.crt
    rsa_private_key_file=/etc/vsftpd/wildcard.13x.fr.pem
    allow_anon_ssl=YES
    force_anon_logins_ssl=YES
    force_anon_data_ssl=YES
    #force_local_logins_ssl=YES
    #force_local_data_ssl=YES
    
    Ensuite il suffit d'adapter pour chacune des instances :
    [root@Pyxis vsftpd]# diff conf_ipv4*
    4,5c4,5
    < implicit_ssl=NO
    < listen_port=21
    ---
    > implicit_ssl=YES
    > listen_port=990
    [root@Pyxis vsftpd]# diff conf_ipv4_essl.conf conf_ipv6_essl.conf
    2,3c2,3
    < listen=YES
    < listen_ipv6=NO
    ---
    > listen=NO
    > listen_ipv6=YES
    6,7d5
    < #pasv_addr_resolve=no
    < pasv_address=78.232.184.204 #vela.lxdr.net
    
    Avec une jolie banner :
    [root@Pyxis vsftpd]# cat banner.txt
    Hey, Listen !
    \\
    .........|''-,............................................,-''|
    .........|:.:.\........................................./:.:|
    .........'|:\:|:.\...................................../:\,/.|
    ..........'|:.'-,:.\................................./:.\/:.'/
    ...........\,,_\:.:\............................./-,:/,--:|...,-~~~~~~~~~-,
    .............'\,_\,-:'\........................./:./',,---/'...| .Look! Listen! |
    ................''-,:\:.\.....,,--~~--,,...../:,-'_:.,-'.....'~-----,. . .,-----~'
    ....................''-,\.\,-'':.. . . . ..:''-/,-/,,--'''...........,,-'. . ,-'
    ....C201............'''',':.. . . . . . ..::','''............--''',,--~'''
    .........................;:.. . . . . . . ..::;.__
    ...................,-~---',:. . . . . . ..::,''''.\:-,'''''/
    ...................'--,/,,'-''-,,:. . . ..:,,-'.¯'''''~~'
    ...............................¯'''''''''''''¯
    //

    Et la configuration réseau :
    iptables -t mangle -A OUTPUT -p tcp --sport 20:21 -j MARK --set-mark 21
    iptables -t mangle -A OUTPUT -p tcp --sport 990 -j MARK --set-mark 21
    iptables -t mangle -A OUTPUT -p tcp --sport 9800:9850 -j MARK --set-mark 21
    
    ip6tables -I INPUT 5 -p tcp --dport ftp -m state --state NEW -j ACCEPT
    ip6tables -I INPUT 6 -p tcp --dport ftps -m state --state NEW -j ACCEPT
    ip6tables -I INPUT 7 -p tcp --dport 9800:9850 -m state --state NEW -j ACCEPT
    ip6tables -t mangle -A OUTPUT -p tcp --sport 20:21 -j MARK --set-mark 21
    ip6tables -t mangle -A OUTPUT -p tcp --sport 990 -j MARK --set-mark 21
    ip6tables -t mangle -A OUTPUT -p tcp --sport 9800:9850 -j MARK --set-mark 21
    service iptables save
    service ip6tables save
    
    Les paquets sont marqués, il reste à les filtrer :
    [root@Pyxis etc]# cat init.d/perso_ftp-throttling
    #!/bin/sh
    
    DEV=eth0
    MAX=100mbit
    FTP=90kbps
    
    if [ $# -ne 1 ]; then
        echo "Usage: "$0" (start|stop|status|monitor)"
        exit 1
    fi
    
    case "$1" in
    start)
      echo "TC start dev $DEV"
      # QDisc HTB principal, par défaut la sous classe 1 est utilisée
      tc qdisc add dev $DEV root handle 13:0 htb default 1
    
      # Ses classes, permettant de brider le débit
      tc class add dev $DEV parent 13:0 classid 13:1 htb rate $MAX
      tc class add dev $DEV parent 13:0 classid 13:2 htb rate $FTP
    
      # Et on conserve les prio en fonction du TOS
      tc qdisc add dev $DEV parent 13:1 handle 131 prio
      tc qdisc add dev $DEV parent 13:2 handle 132 prio
    
      # Les flux FTP marqués (21) sortants sont envoyés dans la classe 2 qui bride
      tc filter add dev $DEV parent 13: protocol ip handle 21 fw classid 13:2
      tc filter add dev $DEV parent 13: protocol ipv6 handle 21 fw classid 13:2
    
      # Marquage des flux, table mangle (utilisée pour l'alteration des paquets)
      #iptables -t mangle -A OUTPUT -p tcp --sport 20:21 -j MARK --set-mark 21
      #iptables -t mangle -A OUTPUT -p tcp --sport 990 -j MARK --set-mark 21
      #iptables -t mangle -A OUTPUT -p tcp --sport 9800:9850 -j MARK --set-mark 21
      #ip6tables -I INPUT 5 -p tcp --dport ftp -m state --state NEW -j ACCEPT
      #ip6tables -I INPUT 6 -p tcp --dport ftps -m state --state NEW -j ACCEPT
      #ip6tables -I INPUT 7 -p tcp --dport 9800:9850 -m state --state NEW -j ACCEPT
      #ip6tables -t mangle -A OUTPUT -p tcp --sport 20:21 -j MARK --set-mark 21
      #ip6tables -t mangle -A OUTPUT -p tcp --sport 990 -j MARK --set-mark 21
      #ip6tables -t mangle -A OUTPUT -p tcp --sport 9800:9850 -j MARK --set-mark 21
    
    ;;
    
    status)
      echo -e "\\tTC status dev $DEV"
      echo
      tc qdisc show dev $DEV
      tc class show dev $DEV
      tc filter show dev $DEV
    ;;
    
    monitor)
      CMD="
        echo -e \\\t\\\tTC dev $DEV;
        echo -e \\\tQDisk;
        tc -s qdisc show dev $DEV;
        echo;
        echo -e \\\tClass;
        tc -s class show dev $DEV
      "
      watch -n1 "$CMD"
    ;;
    
    stop)
      echo "TC stop dev $DEV"
      tc qdisc del dev $DEV root
    ;;
    
    *)
      echo "Usage: "$0" (start|stop|status|monitor)"
      exit 1
    ;;
    esac
    
    exit 0
    

    [root@Pyxis etc]# cat rc.local
    #!/bin/sh
    #
    # This script will be executed *after* all the other init scripts.
    # You can put your own initialization stuff in here if you don't
    # want to do the full Sys V style init stuff.
    
    touch /var/lock/subsys/local
    
    /etc/init.d/perso_ftp-throttling start
    
    
    Voila !

    Oh j'ai failli oublier, une anecdote et des liens utiles, plein :
    VRRP, c'est bien mais pas que :
    0-1024 -> 192.168.0.240 (ip virtuelle vrrp)
    9000-9999 -> 192.168.0.9 (ip réelle)

    Connexion au FTP, port 21 = utilisation de l'IP virtuelle pour arriver dessus

    vsftpd, je lui dis pas quelle est mon IP externe, c'est le NAT de la box qui s'amuse a trafiquer ça dans les paquets pour que le mode passif fonctionne (Passive Mode (192,168,0,240,37,251) -> Passive Mode (78,232,184,204,37,251)) ; mais en SSL, la box ne peut plus toucher aux paquets et alors forcément ça foire en passif.

    Donc je met pasv_address=78.232.184.204, comme ça vsftpd donne bien directement l'ip externe (la box n'a plus à toucher), le client se connecte port 21 (->0.240), demande a passer en passif (port 9xxx -> 0.9) et comme vsftpd s'attend a recevoir la connexion passive sur l'interface 0.240 (comme les commandes), mais que les ports 9xxx vont vers l'ip réelle 0.9, ben ça foire :)

    Bref, maintenant tout fonctionne (et vivement que le NAT saute avec IPv6).

    http://linux-ip.net/articles/Traffic-Control-HOWTO/
    http://www.linuxembedded.fr/2011/06/utiliser-tc-pour-optimiser-lupload/
    http://www.cyberciti.biz/faq/linux-traffic-shaping-ftp-server-port-21-22/
    http://mirrors.bieringer.de/Linux+IPv6-HOWTO/x2523.html
    http://download.pureftpd.org/pub/pure-ftpd/doc/FAQ * Global bandwidth limitation.

    http://sys-log.bencane.com/2012/07/tc-adding-simulated-network-latency-to-your-linux-server/
    http://www.linuxfoundation.org/collaborate/workgroups/networking/netem

    http://lartc.org/howto/lartc.qdisc.filters.html
    http://www.inetdoc.net/guides/lartc/

    http://linux-ip.net/articles/Traffic-Control-HOWTO/classful-qdiscs.html
    htb hfsc prio cbq
    pfifo, bfifo, pfifo_fast, sfq, esfq, gred, tbf
    drr, red

    http://www.inetdoc.net/guides/lartc/lartc.cookbook.ultimate-tc.html
    http://www.inetdoc.net/guides/lartc/lartc.qdisc.html
    http://www.inetdoc.net/guides/lartc/lartc.qdisc.classful.html
    http://www.inetdoc.net/guides/lartc/lartc.cookbook.icmp-ratelimit.html
    http://www.inetdoc.net/guides/lartc/lartc.qdisc.filters.html
    http://opalsoft.net/qos/DS.htm Linux Queuing Disciplines

    http://linuxfr.org/wiki/Une-introduction-au-contrôle-du-trafic-réseau-avec-Linux
    http://www.cyberciti.biz/faq/linux-traffic-shaping-ftp-server-port-21-22/
    http://www.linuxembedded.fr/2011/06/utiliser-tc-pour-optimiser-lupload/

    http://linuxmanpages.net/manpages/fedora14/man8/tc.8.html
    http://linuxmanpages.net/manpages/fedora14/man8/tc-prio.8.html

    http://mirrors.bieringer.de/Linux+IPv6-HOWTO/x2523.html
    http://linuxfr.org/wiki/pages?page=6
    http://linuxfr.org/wiki/Exemple-de-script-pour-de-la-QoS
    http://wiki.openwrt.org/doc/howto/packet.scheduler/packet.scheduler