martes, 18 de septiembre de 2018

Resolviendo problemas con nginx en zimbra 8.0.6

Yo entiendo que usar una versión tan vieja tendrá sus problemas, pero como no tengo tiempo para dedicar a revisar una actualización, pierdo mi tiempo en este tipo de cosas:

Los usuarios se quejan (Como usuarios, es su única tarea) de que al intentar descargar [correos con] archivos [adjuntos] grandes hay problemas.
Así que configuro Thunderbird, dispuesto a esperar que baje todos los correos que ahora están en mi cuenta; pero resulta que no pasa nada. Ha bajado unos cuantos nada más; al hacer click en "Recibir mensajes" vuelve a bajar un par más. Incluso a veces, al hacer click, no baja ninguno.

Después de revisar los logs, logró hallar un único problema en /opt/zimbra/log/nginx.log:
2018/09/18 08:48:07 [info] 10103#0: *258 client logged in, client: 192.168.2.20 using starttls, server: 0.0.0.0:110, login: "alortiz", upstream: 10.10.20.2:7995 (192.168.2.20:45070-10.10.20.2:110) <=> (10.10.20.2:51452-10.10.20.2:7995)
2018/09/18 08:48:31 [error] 10103#0: *258 SSL_read() failed (SSL: error:1408F119:SSL routines:SSL3_GET_RECORD:decryption failed or bad record mac) while proxying and reading from upstream, client: 192.168.2.20 using starttls, server: 0.0.0.0:110, login: "alortiz", upstream: 10.10.20.2:7995 (192.168.2.20:45070-10.10.20.2:110) <=> (10.10.20.2:51452-10.10.20.2:7995)
2018/09/18 08:48:31 [info] 10103#0: *258 proxied session done, client: 192.168.2.20 using starttls, server: 0.0.0.0:110, login: "alortiz", upstream: 10.10.20.2:7995 (192.168.2.20:45070-10.10.20.2:110) <=> (10.10.20.2:51452-10.10.20.2:7995)
La segunda línea es la que cuenta. Mi primera sospecha, acorde a ZCS 8.0.2 Proxy Issues with downloading large mails via pop, es la configuración de SSLv3.
Después de revisar la guía que seguí para deshabilitarlo, he encontrado que el problema se debe a una configuración no contemplada: La comunicación entre el Nginx (Proxy) con POP3. La recomendación más usual es deshabilitar SSL en esa comunicación, lo que no es tan grave considerando que es una instalación en un sólo servidor, así que nada, el hecho que haya datos en texto claro no es tan grave (Que lo es).
Así que todo esto se ha solucionado al correr como usuario zimbra:
zmprov ms mail.salud.gob.sv zimbraReverseProxySSLToUpstreamEnabled FALSE 
Una vez configurado, posiblemente haya que reiniciar el proxy
zmproxyctl restart
Fuentes: (La idea es bien recurrente)
ZCS 8.0.2 Proxy Issues with downloading large mails via pop
zimbra server delivering email to pop3 clients timeout 

miércoles, 14 de marzo de 2018

FTP Pasivo con Iptables

Que sí, es penoso que a estas alturas del siglo se siga configurando FTP como una especie de solución para almacenamiento.

El objetivo de este post es resolver cierto problema con los permisos que se configuran en nuestro firewall con iptables para el FTP pasivo. Que básicamente no funcionan.

El FTP pasivo se beneficia de las reglas para tráfico "de respuesta" que identificamos mediante el módulo conntrack
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
En nuestra cadena FORWARD de FILTER al hablar de nuestro firewall; esto, una vez que hayamos cargado el módulo ip_conntrack_ftp y, hasta donde entiendo, nf_nat_ftp.
modprobe nf_nat_ftp
modprobe ip_conntrack_ftp
Para hacerlo permanente:
sed -i '$a ip_conntrack_ftp\nnf_nat_ftp ' /etc/modules
Aún con eso sigue sin funcionar. La solución la encontré en Iptables to allow incoming FTP: Debe configurarse el kernel con net.netfilter.nf_conntrack_helper=1.  Yo soy más de la idea de ponerlo directamente en /etc/sysctl.conf, pero en realidad puede ir al gusto:
sed -i '$a net.netfilter.nf_conntrack_helper=1' /etc/sysctl.conf

martes, 19 de diciembre de 2017

Polymer: Verificando shadow DOM

Podría ser algo seriamente trivial, pero decidí revisar que tanto significa lo de shadow DOM para Polymer. Así que aplique un estilo CSS tanto ridículo, tanto lo suficiente para verificar lo que quiero.

En src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html, agrego un ligero estilo y una pequeña línea, de tal forma que el fichero queda de la siguiente forma:
        
<link href="../../bower_components/polymer/polymer-element.html" rel="import"></link>
<link href="/src/componentes/entradas/vt-entrada.html" rel="import"></link>
<link href="/src/componentes/etiquetas/vt-etiqueta.html" rel="import"></link>

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        p,label {
            background-color: aliceblue;
        }
        </style>

            <h2>
Hello [[prop1]]!</h2>
<vt-entrada contenido="{{contenido}}"></vt-entrada>
            <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
            Este es texto desde el componente padre polymer-muestra-inicial-app<br />
        
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Luego, src/componentes/entradas/vt-entrada.html ha quedado de la siguiente forma: Y sí, sólo agregue un color de fondo y subrayado para el texto:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
            p {
                background-color: beige;
                text-decoration: underline; 
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                        notify: true,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
El resultado viene siendo elsiguiente:
Que básicamente viene a confirmar que:
  • Los estilos no suben desde un componente hijo a un componente padre. Lo que se esperaba pues. Esto se puede demotrar mejor si se elimina el estilo en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html
  • Parece que los estilos no bajan desde el componente padre al componente hijo. Esto se puede demostrar si se elimina el estilo en src/componentes/entradas/vt-entrada.html
Esto último me parecía un poco preocupante, así que decidí maquetar un poco para ver como se comporta. Spoiler: Lo hace tal como se espera
Primer instalamos Pure mediante bower de la siguiente forma:
bower install pure --save
Luego, modificamos a src/index.html ara agregar los estilos de Pure:
 
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

        <title>polymer-muestra-inicial</title>
        <meta name="description" content="Breve descripción de mi persona">

        <!-- See https://goo.gl/OOhYW5 -->
        <link rel="manifest" href="/manifest.json">

        <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

        <link rel="import" href="/src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html">
        <link rel="stylesheet" href="/bower_components/pure/base.css">
        <link rel="stylesheet" href="/bower_components/pure/grids.css">
        <link rel="stylesheet" href="/bower_components/pure/grids-responsive.css">
    </head>
    <body>
            <polymer-muestra-inicial-app></polymer-muestra-inicial-app>
    </body>
</html>
Y hacemos la maquetación dentro de polymer-muestra-inicial-app en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html de la siguiente forma:
 
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        p,label {
            background-color: aliceblue;
        }
        </style>
        <div class="pure-g">
            <div class="pure-u-1 pure-u-md-2-5">
                <h2>Hello [[prop1]]!</h2>
            </div>
            <div class="pure-u-1 pure-u-md-3-5">
                    <vt-entrada contenido="{{contenido}}"></vt-entrada>
                    <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
                    <p>Este es texto desde el componente padre polymer-muestra-inicial-app</p>        
            </div>
        </div>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Que ahora se ve de la siguiente forma:
Así que supongo que maquetar bien, descender clases bien, pero sobre los detalles específicos y generales de como va funcionando esto tendrán que ir esperando un poco más

viernes, 15 de diciembre de 2017

Siguiendo con la Introducción a Polymer 2

Lo de los webcomponents es algo genial: Dividimos la complejidad en pequeños pequeños, cuyas soluciones pueden ser reutilizables. El siguiente paso es verificar no sólo como trabaja la relación padre-hijo, sino la de aquellos componentes que pudieran tener el mismo nivel jerárquico en un momento dado.

Sobre nuestro ejemplo anterior, haremos un nuevo componente vt-etiqueta
mkdir src/componentes/etiquetas/
El contenido del fichero src/componentes/etiquetas/vt-etiqueta.html es el siguiente:
<link href="../../../bower_components/polymer/polymer-element.html" rel="import"></link>
<dom-module id="vt-etiqueta">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>
        <label>Etiqueta componente: [[contenido]] </label>
    </template>
    <script>
        class VtEtiqueta extends Polymer.Element {
            static get is() {
                return 'vt-etiqueta';
            }
            static get properties (){
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }
        window.customElements.define(VtEtiqueta.is, VtEtiqueta);
    </script>
</dom-module>
Luego, hemos de añadirlo en el componente polymer-muestra-inicial-app, que tiene la mayor jerarquía en nuestra aplicación. Primero un import en forma de etiqueta <link> y luego una etiqueta propiamente dicha. El código de src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html queda ahora de la siguiente forma:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>

        <h2>Hello [[prop1]]!</h2>
        <vt-entrada contenido="{{contenido}}"></vt-entrada>
        <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>

El contenido de vt-etiqueta, expuesto en la propiedad contenido será actualizado desde nuestro componente anterior, vt-entrada.
Para que eso suceda, modificamos la definición de este último en src/componentes/entradas/vt-entrada.html, configurando el atributo notify de dicha propiedad a true. Una pequeña línea no debería ser razón para volver a presentar todo el código, pero es gratis:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                        notify: true,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
Al escribir en la caja de texto del componente vt-entrada, el contenido de vt-etiqueta se va modificando.

Para tener en mente según lo visto:

  • Los binding [[propiedad]] son unidireccionales. (Padre al hijo)
  • Los binding {{propiedad}} son bidireccionales. (Padre al hijo y viceversa)
  • Para que esto último funcione con propiedades que han de modificarse en un componente hijo, es preciso que la propiedad tenga configurada notify: true en dicho hijo.
Sí, los componente son bastante reutilizables
Para ejemplo, basta con agregar otro vt-entrada en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html cuyo contenido haga binding bidireccional con prop1, que es la propiedad que muestra del Hola Mundo de nuestra aplicación. Es un pequeño cambio, pero como copiar y pegar es sencillo:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>

        <h2>Hello [[prop1]]!</h2>
        <vt-entrada contenido="{{contenido}}"></vt-entrada>
        <vt-entrada contenido="{{prop1}}"></vt-entrada>
        <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Desde el navegador, lo vemos de la siguiente forma:
Seguimos sin hacer que esto parezca algo mínimamente serio, pero al menos se esta volviendo cada vez más divertido.

jueves, 14 de diciembre de 2017

Especie de Introducción a Polymer 2

Este artículo debió llamarse "Especie de introducción a AngularJS". Es decir, estuve trabajando por mucho tiempo en una aplicación con AngularJS como framework: Fue una muy buena experiencia, y ha sido mi culpa empezar a aprender algo que años antes ya se sabía iba a quedar obsoleto por rediseño. Todos los cambios me superan: Claro que así descritos parecen una mejora, pero el sólo hecho de cambiar a TypeScript me hizo repensar desde cero toda la aplicación.

Así que reconsideré a Polymer: Esta basado en webcomponents, algo que ya había alcanzado a vislumbrar con AngularJS 1.5. Webcomponents aspira a ser el futuro de la web, bueno, así que veremos que tal se comporta en el presente.

Para instalarlo en Fedora, bastará con correr los siguiente comandos como root. Para npm, -g implica global, con lo que se supone que otros usuarios en el sistema podrían usar las herramientas así instaladas:
dnf install npm 
npm -g install bower
npm -g install polymer-cli
Luego seguimos las indicaciones que nos hace la gente de polymer. Desde consola, como cualquier usuario disponible en el sistema y ubicados desde cualquier parte que querramos (Preferentemente no "cualquier" parte), ejecutamos lo siguiente:
mkdir polymer-muestra-inicial
cd polymer-muestra-inicial/
polymer init --name polymer-2-application
Donde --name permite seleccionar la plantilla inicial para nuestro proyecto. polymer-2-application arranca con poco código, lo que es conveniente para aprender lo más posible.
polymer init --name polymer-2-application
info:    Running template polymer-init-polymer-2-application:app...
? Application name polymer-muestra-inicial
? Main element name polymer-muestra-inicial-app
? Brief description of the application Breve descripción de mi persona
A continuación, se instalan todos los paquetes necesarios para nuestra aplicación mediante bower, y es mediante él que debemos agregar nuevas dependencias.
En este momento, podemos probar lo que tenemos hasta ahora (Casi nada a decir verdad) ejecutando desde consola:
polymer serve --open
No subestimen este comando: Incluso permite servir contenido como HTTPS.

Ahora empezamos la introducción propiamente dicha: 

Aclaro que a este momento sigo sin pensar en una estructura de directorios para el código, así que voy a separarlo en componentes tanto como sea posible. 
mkdir -p src/componentes/entradas
cd src/componentes/entradas
Además, voy a trabajar modificando el componente que se ha creado originalmente para esta aplicación: polymer-muestra-inicial-app, no tanto por añorar mi época con AngularJS, sino por aprovechar todo el conocimiento previo de dicha época.

Así, creamos el fichero src/componentes/entradas/vt-entrada.html, que define un input casi sin modificación alguna, con el siguiente contenido
<link href="../../../bower_components/polymer/polymer-element.html" rel="import"></link>

<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido}}" />
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module> 
A ver, para explicaciones más detalladas las teoría. Desde aquí, sin embargo, podemos decir:
  • Esta es casi, casi la forma en que se define un webcomponent nativo al día de hoy: Dentro de <template></template> tenemos el HTML de toda la vida; dentro de <script></script> la definición de nuestro componente. Todo dentro de <dom-module>
  • properties define las propiedades de este componente, en resumen, las variables con las que nos relacionamos con otros componente y que controlamos mediante código. En realidad, extenderse en la teoría de este tema con la documentación oficial es un muy buen primer paso a tomar.
  • El estilo CSS definido dentro de <style> se circunscribe a este componente, mediante una técnica llamada DOM Shadow que hoy en día ya sirve para muchas otras cosas además de encapsular CSS (Igual nace por esta necesidad específica)
Ahora modificamos el componente que se venía de la plantilla, básicamente haciendo un import y usando nuestro componente. Al final se tiene que ver de la siguiente forma:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">

<dom-module id="polymer-muestra-inicial-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h2>Hello [[prop1]]!</h2>
    <vt-entrada contenido="Configurado desde plantilla padre"></vt-entrada>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
      static get is() { return 'polymer-muestra-inicial-app'; }
      static get properties() {
        return {
          prop1: {
            type: String,
            value: 'polymer-muestra-inicial-app'
          }
        };
      }
    }

    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
  </script>
</dom-module>
Al verlo en la navegador, tenemos algo como esto:

Ahora hacemos que esta introducción valga la pena:

Visto así,  polymer casi nos parece un sistema de plantillas estático, así que para despertar el interés haremos unos cambios en nuestro componente vt-entrada: Los cambios se realizan en la plantilla, agregando un <p></p> cuyo contenido se hace refencia a la propiedad contenido de nuestro componente.

Por otra parte, vamos a cambiar el data binding de nuestro <input> para que funcione en forma bidireccional: En lugar de contenido, ahora será contenido::input.

Lo último a resaltar es que tenemos dos forma de hacer los binding: En una vía mediante llaves y bidireccional mediante corchetes. Esto es otro tema bastante complicado en el que se debería extender con la documentación oficial. Al final, nuestro componente debe verse de la siguiente forma:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">

<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
Lo vemos de la siguiente forma en el navegador:
Y sí, el contenido se actualiza conforme se escribe en la caja de texto. Así como que ya dan más ganas de trabajar con esto.

Supongo que para una breve introducción ya está.

viernes, 1 de diciembre de 2017

Otro intento para configurar QoS en Linux: Día 2

Hasta el momento y a grandes rasgos, hemos limitado el uso de ancho de banda a un límite especifíco. Luego, hemos modelado la cola para que esta reparta el uso de ancho de banda de manera casi equitativa entre todas las conexiones: Dicha política se verifica para todo el tráfico en nuestra red.

Lo siguiente será hacer una configuración que planteé precisamente el significado de QoS: Clasificaremos tráfico y vamos a aplicarle políticas específicas a cada tipo de tráfico. Puede pensarse un poco al revés: Estableceremos políticas y luego vamos a especificar a que tráfico le aplicamos cada una.

El primer intento de configuración va de la siguiente forma:
#!/bin/bash

## Borramos la configuración anterior. 
## Cuando se tenga la configuración por defecto, se lanza un mensaje de error "RTNETLINK answers: No such file or directory"
tc qdisc delete dev ens2 root

## Cambiamos qdisc de fast_fifo a htb. 
## Dentro del esquema de HTB, este es llamado ROOT
tc qdisc add dev ens2 root handle 1:0 htb default 10

## Agregamos la primera clase. 
## Dentro del esquema de HTB, este es llamado INNER. No hay shapping en este momento, pero es necesario configurarle para el funcionamiento de los leaf
tc class add dev ens2 parent 1:0 classid 1:1 htb rate 20480 ceil 20480

## Dentro del esquema de HTB, este es llamado LEAF. Este determina la política por defecto 
tc class add dev ens2 parent 1:1 classid 1:10 htb rate 2540 ceil 2540
### Creamos los nuevos LEAF que describen dos nuevas políticas para el tráfico. 
tc class add dev ens2 parent 1:1 classid 1:20 htb rate 7680 ceil 7680
tc class add dev ens2 parent 1:1 classid 1:22 htb rate 10240 ceil 10240

## Dentro de cada clase LEAF, agregamos una QDISC de tipo SFQ. 
## SFQ intentará repartir el ancho de banda de forma más o menos equitivativa
tc qdisc add dev ens2 parent 1:10 handle 130: sfq perturb 5
tc qdisc add dev ens2 parent 1:20 handle 140: sfq perturb 5
tc qdisc add dev ens2 parent 1:22 handle 150: sfq perturb 5

## Filtro en tc
## Se especifica mediante los filtros de tc que tráfico se envía a que política
## Usamos el filtro handle, es bastante simple y económico en términos de procesamiento
tc filter add dev ens2 parent 1:0 prio 0 protocol ip handle 20 fw flowid 1:20
tc filter add dev ens2 parent 1:0 prio 0 protocol ip handle 22 fw flowid 1:22

## Pre-filtro en iptables
## Este es precisamente el filtraje del tráfico: Marcamos con iptables que tráfico es cada cosa
iptables -t mangle -F
iptables -t mangle -A POSTROUTING -p tcp -m multiport --sport 22 -j MARK --set-mark 20
iptables -t mangle -A POSTROUTING -p tcp -m multiport --dport 80,443 -j MARK --set-mark 22
Las clases por ahora se ven de la siguiente forma:
tc class show dev ens2
class htb 1:22 parent 1:1 leaf 150: prio 0 rate 10240bit ceil 10240bit burst 1600b cburst 1600b
class htb 1:1 root rate 20480bit ceil 20480bit burst 1600b cburst 1600b
class htb 1:10 parent 1:1 leaf 130: prio 0 rate 2536bit ceil 2536bit burst 1599b cburst 1599b
class htb 1:20 parent 1:1 leaf 140: prio 0 rate 7680bit ceil 7680bit burst 1599b cburst 1599b
La configuración consta de dos políticas implicítas y una por defecto. Limitan el ancho de banda: El tráfico SSH (Por ahora tendremos que servirnos de este) a 750kbps; tráfico web a 1 Mbps y el demás tráfico, todo aquello que no hallamos considerado, a 250kbps. Revisemos como es que funciona una configuración de este tipo
Un ping con alta latencia. Veamos que pasa al agregar un poco de tráfico HTTPS
La latencia del ping no cambia tanto como se espera, por otro lado, la descarga web parece ir bastante bien. Al agregar un poco de tráfico FTP, bastante conocido por saber acaparar el ancho de banda, tenemos lo siguiente
El uso de ancho de banda para FTP esta bastante contenido y apenas hizo mella en la descarga HTTP. Por ahora hemos verificado que los límites para el ancho de banda en realidad funciona. Hay un problema al que no pude sacarle captura de pantalla pero que se adivina en la alta latencia del ping: DNS puede llegar a dar timeout en estas condiciones. Esto es por la naturaleza misma del tráfico DNS. La solución a este problema es establecer prioridades para las políticas. Por defecto, todos tienen prioridad 0, así que en realidad lo que se hace es quitarle prioridad a ciertas reglas con la opción prio
...
## Dentro del esquema de HTB, este es llamado LEAF. Este determina la política por defecto 
tc class add dev ens2 parent 1:1 classid 1:10 htb rate 2540 ceil 2540 prio 1
### Creamos los nuevos LEAF que describen dos nuevas políticas para el tráfico. 
tc class add dev ens2 parent 1:1 classid 1:20 htb rate 7680 ceil 7680 prio 0
tc class add dev ens2 parent 1:1 classid 1:22 htb rate 10240 ceil 10240 prio 1
... 
Y marcar el tráfico ICMP de la siguiente forma
...
iptables -t mangle -A POSTROUTING -p icmp -j MARK --set-mark 20
...
Ahora, la tasa de transferencia para HTTP sigue con el mismo rendimiento: Es el tráfico ICMP el beneficiado, es posible ver como la latencia ha bajado considerablemente
En este punto, las políticas garantizan una máxima tasa de transferencia para el tráfico que así lo necesita, y la menor latencia posible para el tráfico que así lo requiera.

miércoles, 25 de octubre de 2017

Otro intento para configurar QoS en Linux: Día 1

Nota: Pues que creo que he tenido problemas con las capturas de pantalla y no se corresponden del todo con el trabajo en curso. Igual lo publico, así las cosas. Excepto ese pequeño detalle, todo lo demás es funcional

La idea es que modelemos el tráfico de nuestra red aplicando la siguiente configuración en nuestro firewall linux. O en nuestro IPS inline, que no es tan mala idea como en un principio lo parece. Que incluso puede hacerse una cajita específicamente para esto: Lo importante es que la configuración de este equipo pueda modelar todo el tráfico de nuestra red, al pasar toda nuestra red mediante él.

Por defecto, nuestra configuración tiene la siguiente forma:
tc qdisc show 
qdisc noqueue 0: dev lo root refcnt 2
qdisc pfifo_fast 0: dev ens2 root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: 10.dev ens3 root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev ens4 root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev ens9 root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
El usuario lo vería de la siguiente manera:

Empezamos con lo que podría considerarse una configuración mínima para hacer shaping:
#!/bin/bash

## Borramos la configuración anterior. 
## Cuando se tenga la configuración por defecto, se lanza un mensaje de error "RTNETLINK answers: No such file or directory"
tc qdisc delete dev ens2 root

## Cambiamos qdisc de fast_fifo a htb. 
## Dentro del esquema de HTB, este es llamado ROOT
tc qdisc add dev ens2 root handle 1:0 htb default 10

## Agregamos la primera clase. 
## Dentro del esquema de HTB, este es llamado INNER. No hay shapping en este momento, pero es necesario configurarle para el funcionamiento de los leaf
tc class add dev ens2 parent 1:0 classid 1:1 htb rate 20480 ceil 20480

## Dentro del esquema de HTB, este es llamado LEAF. Acá ocurre el shapping por primera vez
tc class add dev ens2 parent 1:1 classid 1:10 htb rate 20480 ceil 20480

## Dentro de la última (Y única por ahora) clase LEAF, agregamos una QDISC de tipo SFQ. 
## SFQ intentará repartir el ancho de banda de forma más o menos equitivativa
tc qdisc add dev ens2 parent 1:10 handle 130: sfq perturb 5


Ahora, estamos configurados de la siguiente forma:
tc qdisc show dev ens2
qdisc htb 1: root refcnt 2 r2q 10 default 10 direct_packets_stat 0 direct_qlen 1000

tc class show dev ens2
class htb 1:10 parent 1:1 leaf 130: prio 0 rate 20480bit ceil 20480bit burst 1600b cburst 1600b
class htb 1:1 root rate 20480bit ceil 20480bit burst 1600b cburst 1600b
El resultado es que ahora nos vamos limitar: 20480bit representa un enlace de 2 MB/s. Desde el punto de vista del cliente nos vemos de la siguiente forma: Al iniciar ambas peticiones casi al mismo tiempo, el uso de ancho de banda se reparte casi igual
Conforma pasa el tiempo, el tráfico para FTP-DATA consigue un mejor rendimiento. Luego, también podría ocurrir si se inicia la descarga de FTP primero:
En general, con pfifo_fast ocurrirá que algunas conexiones irán mejor que otras. Posiblemente las más nuevas (Esto es bien común de verificar). Algunos protocolos sbbre otros. Para tener mejor idea, descargo el mismo contenido usando HTTP en ambos clientes
El cliente a la derecha descarga con todo el ancho de banda disponible en ese momento

Cuando empieza la descarga en el cliente a la izquierda, este es claramente beneficiado de un mejor ancho de banda

Una solución a este problema (Que el ancho de banda se reparta mejor entre todas las conexiones), es usar una disciplina de cola en la clase leaf. SFQ es bastante sugerido para esta trabajo en específico. Agremos lo siguiente al final del script anterior

## Dentro de la última (Y única por ahora) clase LEAF, agregamos una QDISC de tipo SFQ.
## SFQ intentará repartir el ancho de banda de forma más o menos equitivativa
tc qdisc add dev ens2 parent 1:10 handle 130: sfq perturb 5
Una vez configurado así, no importa en que momento inicia la descarga el uno o el otro: Ambas conexiones tienen casi el mismo ancho de banda disponible. El "casi" es importante. SFQ "intenta" darles el mismo ancho de banda a ambas conexiones. Con todo lo que lleva en contra, parece que hace un trabajo bastante aceptable

Otros apuntes interesantes

Otros apuntes interesantes