"Después del juego es antes del juego"
Sepp Herberger

jueves, 30 de abril de 2015

OpenWrt+USBIP+Smartboard 680 (Episodio I)

Ye he relatado mas de una vez la relación de amor/odio que tengo con las pizarras SmartBoard. En este caso me tocó enfrentarme con una SmartBoard 680: recordemos que tanto la alimentación como el flujo de datos van por el mismo cable USB, de tal forma que si el cable es demasiado largo (mas de 2 o 3 metros) la conectividad de la pizarra se resiente bastante, perdiendo la señal continuamente y haciendo inmanejable el instalache.

En este caso tengo un aula con una organización peculiar que hace que la distancia del PC a la pizarra fuese de mas de 10m. En principio compramos un cable USB activo, porque nos aseguraron que eso iba a funcionar. Ni de coña: la señal de la pizarra era solo un poco mas estable, pero al final acababa perdiendose la conexión y fallando el driver en el momento mas inoportuno.

¿Estaba todo perdido?. Pues no nos resignamos: el que no llegue el cable USB no quiere decir que no se pueda meter la señal USB por un ordenador o dispositivo, encapsularla en paquetes TCP/IP y hacerla llegar hasta el ordenador del profesor en el cual está el software de la pizarra: es lo que se llama USBIP. La idea es sencilla:

  • Conectamos la pizarra a la entrada USB un "servidor usbip" que esté cercano, para poder usar un cable corto que nos garantice una conexión estable.
  • En ese "servidor usbip" comparte  desde su IP la conexión USB a la pizarra mediante el demonio "usbipd".
  • El ordenador del profesor actúa de "cliente usbip" y se conecta al dispositivo USB compartido desde el "servidor usbip" mediante el programa cliente  "usbip", creando un puerto usb virtual, que está enganchando mediante un túnel TCP con la conexión USB de la pizarra.
  • El resultado final es que se engaña al driver, que se piensa que la pizarra (o el dispositivo que sea) está efectivamente conectada a un puerto USB del ordenador del profesor.

Un esquema visual:

¿Que utilizamos como "servidor usbip"?. Primero pensé en algún PC antiguo, con pocos recursos ya que no se necesita mucho hardware para ejecutar el servicio. Luego pensé en una Raspberry Pi, que además es pequeña y resultona. Pero al final escogí lo mas divertido: podía usar un router ADSL viejo que tuviese entrada USB y permitiese instalar OpenWrt, ya que tiene soporte para usbip.

El router ADSL elegido es un ARV7518PW, que era un router blanco Astoria que daba Ya.com:

Es un router que tiene un puerto USB, memoria flash de 8Gb y en el que, con ciertas precauciones, se puede instalar OpenWrt. También tenia el modelo anterior, un ARV4518PW de color gris, pero no lo usé ya que solo tiene una memoria flash de 4Gb y andaba un poco justito para meter el Openwrt.

La instalación de OpenWrt es un poco mas complicada que la que hice hace un tiempo para un Huawei, ya que hay que conectar por el puerto serie interno del router y reemplazar el brnboot por uboot (esto es como la bios+grub del router) y enviar la imagen con el OpenWrt. No es difícil siguiendo estas guías paso a paso:

En ambas guías se habla de instalar (o compilar desde cero) la versión 12.09 (Attitude Ajustement) de OpenWrt, pero yo me decanté por usar la versión 14.07 (Barrier Breaker), ya que cuando se hicieron esas guías todavía no había salido una versión estable de ésta. La causa es que el software es mas moderno y que se soluciona un bug en el driver la tarjeta wifi que hacía que solo funcionase a una potencia de 3db, permitiendo ahora ponerla hasta 20db y freir el cerebro de todos los hipocondriacos en varios cientos de metros a la redonda. La URL del sistema Barrier Breaker para este router, ya preparado para flashear es: https://downloads.openwrt.org/barrier_breaker/14.07/lantiq/xway/openwrt-lantiq-xway-ARV7518PW-squashfs.image.

Bueno, una vez tenemos el Barrier Breaker en el router, lo hemos conectado a la red por cable, puesto una IP fija y hemos cambiado la contraseña de root, nos conectamos a él por ssh como a cualquier otro Linux OpenWrt y empezamos a configurarlo para convertirlo en un servidor usbip, vamos allá:

# opkg update
# opkg install kmod-usbip kmod-usbip-client kmod-usbip-server
# opkg install kmod-usb-ohci
# opkg install libsysfs libwrap
# opkg install http://downloads.openwrt.org/attitude_adjustment/12.09/lantiq/danube/packages/usbip_1.1.1-2_lantiq.ipk
# opkg install http://downloads.openwrt.org/attitude_adjustment/12.09/lantiq/danube/packages/usbip-client_1.1.1-2_lantiq.ipk
# opkg install http://downloads.openwrt.org/attitude_adjustment/12.09/lantiq/danube/packages/usbip-server_1.1.1-2_lantiq.ipk
# reboot

Con esto instalamos los drivers y el software, después de reiniciar verificamos si está todo:

# opkg list-installed | grep usbip
kmod-usbip - 3.10.49-1
kmod-usbip-client - 3.10.49-1
kmod-usbip-server - 3.10.49-1
usbip - 1.1.1-2
usbip-client - 1.1.1-2
usbip-server - 1.1.1-2
#

Ahora enchufamos la pizarra al puerto USB del router y vemos si la detecta:

# opkg update
# opkg install usbutils
# lsusb
Bus 001 Device 001: ID 0b8c:0001 SMART Technologies Inc.
#

Ahi está. Veamos si se puede compartir con usbip:

# usbip list -l
Local USB devices
=================
 - busid 1-1 (0b8c:0001)
         1-1:1.0 -> usbip-host
#

Ahi está, conectada al bus 1-1, que es como lo identifica usbip. Para que se comparta el usb cada vez que arranque el sistema operativo del router  lo mejor es meter en /etc/rc.local el código siguiente:

# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

usbipd -D &
sleep 1
usbip bind -b 1-1

exit 0

Son dos instrucciones: "usbipd -D", que arranca el servidor en modo daemon, y "usbip bind -b 1-1" , que enlaza el servidor usbip con el dispositivo conectado al bus 1-1, en este caso la pizarra Smart, de tal manera que dicho dispositivo queda compartido por el demonio.

Reiniciamos y, suponiendo que 172.20.41.57 es la IP del router donde tenemos conectada la pizarra,  al hacer:

# usbip list -r 172.20.41.57
Exportable USB devices
======================
- 172.20.41.57
1-1: SMART Technologies Inc. : unknown product (0b8c:0001)
: /sys/devices/platform/ifxusb_hcd/usb1/1-1
: (Defined at Interface level) (00/00/00)
: 0 - Human Interface Device / No Subclass / None (03/00/00)

Nos aparece la pizarra lista para conectar a ella. Una comprobación mas:

#  netstat -alpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:3240 0.0.0.0:* LISTEN 1079/usbipd
tcp 0 0 0.0.0.0:domain 0.0.0.0:* LISTEN 1015/dnsmasq

Ahí tenemos el demonio usbipd esperando conexiones por el puerto 3240, para exportar la conexión USB de la pizarra a quien quiera conectarse a él.

Bueno, ya tenemos la parte servidora. En la próxima entrada veremos la parte cliente, que puede ser en Windows o en Linux.

 

martes, 21 de abril de 2015

Forzar la impresión en escala de grises en una impresora en color usando tea4cups.

Recientemente hemos adquirido una impresora Brother DCP-9020CDW. Mi intención era definir sobre ella dos colas, que se comportarían como dos impresoras virtuales: una para imprimir en blanco y negro y la otra para imprimir en color. La causa es que si dejo una sola cola, seguramente la mayoría de las veces la impresión será en color por simple descuido o dejadez a la hora de configurar las propiedades del trabajo de impresión, con el consiguiente desperdicio de dinero que necesitamos para pagar el billón de euros de deuda pública.

La primera idea fue manipular el fichero .ppd, haciendo una versión del mismo que dijese que la impresora era en blanco y negro, y usar dicho fichero .ppd para dar de alta la impresora correspondiente. Pues no funcionó: si se manda imprimir en color desde un puesto el trabajo salía en color independientemente de lo que dijese el .ppd que estaba permitdo.

No me quedaba otra que usar tea4cups, esa estupenda navaja suiza para las colas de impresión. Descargamos el fichero de http://www.pykota.com/software/tea4cups/download, lo descomprimimos e instalamos cada cosa en su sitio:

  • El fichero tea4cups en el directorio de backends de impresión: /usr/lib/cups/backend/
  • El fichero tea4cups.conf en el directorio de configuración de cups: /etc/cups/..

Una vez hecho esto, modificamos tea4cups.conf según nuestras necesidades. El fichero está profusamente autodocumentado con varios ejemplos de todas las virguerías que se pueden hacer con las colas de impresión, yo lo he modificado mínimamente añadiendo al original las líneas en rojo:

server:~# cat /etc/cups/tea4cups.conf 
# $Id: tea4cups.conf 120 2006-08-10 21:42:16Z jerome $
#
# Tea4CUPS : Tee for CUPS
.......
[global]
......
debug : yes
........
directory : /var/spool/cups/
.........
prehook_accounting : /root/scripts/seguimiento_impresion
..................................
...................
# posthook_dialog2 : cat >/tmp/result2

#Se incluye un filtro para la cola "BN_SALAPROFESORES" que convierte a BN los trabajos de impresión
#mandados a color, modificando el .prn bruto antes de ser procesado.
[BN_SALAPROFESORES]
filter : sed "0,/@PJL SET RENDERMODE=COLOR/{s/@PJL SET RENDERMODE=COLOR/@PJL SET RENDERMODE=GRAYSCALE/}"

El fichero /root/scripts/seguimiento_impresión del prehook_accounting será el script que se ejecutará cada vez que se mande un trabajo a cualquier impresora de CUPS. En mi caso lo uso para llevar una contabilidad de los trabajos de impresión y para otras tareas de depuración. Su contenido es:

server:~# cat /root/scripts/seguimiento_impresion
#!/bin/bash

function valid_ip()
{
local ip=$1
local stat=1

if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}


#URLEncode para el titulo del documento
TEATITLE=$(python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1])" "$TEATITLE")

#Echo copia en /tmp/ el trabajo de impresión en bruto para depuración.
#cp "$TEADATAFILE" "/tmp/$TEAJOBID.prn"

dominio=$(host -d -t CNAME servidor | grep domain | cut -f1 -d".")
paginas=$(pkpgcounter "$TEADATAFILE")

#Hay que convertir el valor de TEACLIENTHOST en un nombre de equipo.
if [ "$TEACLIENTHOST" == "localhost" -o "$TEACLIENTHOST" == "" ]
then
equipo=$(hostname -s)
else
if valid_ip $TEACLIENTHOST
then
equipo=$(host $TEACLIENTHOST | cut -d" " -f5 | cut -d"." -f1)
else
equipo=$TEACLIENTHOST
fi
fi

#Con todos estos parámetros que vienen del backend de /usr/lib/backend/teac4cups podemos llevar la contabilidad....
#En el ejemplo es un simple fichero de log, pero podría llevarse a un fichero .csv, guardarse en una BBDD o enviarse a una
#aplicacion web.
echo $TEAPRINTERNAME $TEAJOBID $TEAUSERNAME $TEATITLE - $equipo.$dominio - $TEACOPIES `pkpgcounter $TEADATAFILE` $TEAJOBSIZE>>/var/log/printaccounting.log

exit 0

Para que nuestros trabajos de impresión pasen por el backend de tea4cups y sean enviados a este script anterior, hay que modificar su definición en el el fichero /etc/cups/printers.conf, retocando el DeviceURI tal que así (ojo al parche, esto hay que hacerlo con el servicio CUPS parado):

server:~# cat /etc/cups/printers.conf
# Printer configuration file for CUPS v1.5.3
# Written by cupsd
# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING
<Printer BN_SALAPROFESORES>
UUID urn:uuid:b21a4e7f-1d82-35aa-63cc-440dad3b7ada
Info BN_SALAPROFESORES
Location Sala de Profesores-Blanco y Negro
MakeModel Brother DCP-9020CDW CUPS
DeviceURI tea4cups://socket://172.19.196.23
State Idle
..........
</Printer>
<Printer COLOR_SALAPROF>
UUID urn:uuid:2336104b-3317-3748-702f-3063db09d1fe
Info COLOR_SALAPROF
Location Sala de Profesores-Color
MakeModel Brother DCP-9020CDW CUPS
DeviceURI tea4cups://socket://172.19.196.23
State Idle
..........
</Printer>

Siendo 172.19.196.23 la IP de nuestra impresora en la red.

En cuanto a:

filter : sed "0,/@PJL SET RENDERMODE=COLOR/{s/@PJL SET RENDERMODE=COLOR/@PJL SET RENDERMODE=GRAYSCALE/}"

esa línea es la madre del cordero y la que se encarga de dar el cambiazo de color a blanco y negro a cualquier trabajo enviado a la cola de impresión BN_SALAPROFESORES. ¿Cómo funciona esto?. Bueno, pues hay repasar como procesa los datos CUPS:

  • Las aplicaciones mandan los trabajos de impresión a CUPS. Originalmente esos trabajos están en el formato de la aplicación: odt, pdf, jpeg, xls, lo que sea.
  • CUPS procesa con el driver el trabajo de impresión y lo convierte a un lenguaje inteligible por la impresora destino. Este lenguaje puede ser PCL3/4/5, PCLXL, PostScript, ZJS, HBP, etc, etc, etc.
  • Este fichero en bruto en uno de los formatos indicados anteriormente se envía directamente a la impresora, que con su firmware lo interpreta e imprime.

En mi caso se trataba de averiguar en que formato estaba el fichero en bruto enviado para la impresora en cuestión. Eso se hace fácilmente capturando dicho fichero mediante tea4cups, en el script seguimiento_impresion:

......
......
#Echo copia en /tmp/ el trabajo de impresión en bruto para depuración.
cp "$TEADATAFILE" "/tmp/$TEAJOBID.prn"
......
......

Así conseguiremos una copia del fichero en bruto una vez  procesado por el driver en /tmp/$jobid.prn. Simplemente mandamos imprimir algo por la impresora y una vez impreso nos vamos a /tmp/$jobid.prn" y husmeamos que hay dentro. En nuestro caso es lo siguiente (marco en rojo las partes relevantes):

ESC%-12345X@PJL 
@PJL SET REPRINT=OFF
@PJL SET HOLD=OFF
@PJL SET USERNAME="root"
@PJL SET JOBNAME="Manual Centralita 7962G.pdf"
@PJL SET LOGINUSER="root"
@PJL JOB NAME="Manual Centralita 7962G.pdf"
@PJL PRINTLOG ITEM = 1,PRINTER
@PJL PRINTLOG ITEM = 2,Tue,14 Apr 2015 13:47:5
@PJL PRINTLOG ITEM = 3,Administrador
@PJL PRINTLOG ITEM = 4,CONSERJERIA
@PJL SET JOBTIME = "20150414134705"
@PJL SET STRINGCODESET=HPROMAN8
@PJL COMMENT ECONOMODE=OFF
@PJL SET ECONOMODE=OFF
@PJL SET RENDERMODE=COLOR
@PJL SET COLORADAPT=OFF
@PJL SET APTMODE=OFF
@PJL SET LESSPAPERCURL=OFF
@PJL SET FIXINTENSITYUP=OFF
@PJL SET RESOLUTION=600
@PJL ENTER LANGUAGE=XL2HB
) BROTHER XL2HB;1;0
<C0>^@<F8><86><D1>X^BX^B<F8><89>A<C0>^@<F8><88><C0>^A<F8><82>H<C0>^@<F8>(<C0>^A<F8>&<C0>^B<F8>%<C8><C0>^HdRegular<F8>'<C0>^@<F8>4C<D3>d^@d^@<F8>*u<C0>^@<F8>d<C0>^@<F8>b<C1><A0>^R<F8>l<C1><9E>^Z<F8>kѠ^R<9E>^Z<F8>g<C9><C1>^W^@^@^@^C^@^@^@^A^@^E^@^@^@^D^@^H^D^A^@^E^@^A^@^D^@^Q^D^A^@^E^@^B^@^D^@^Q^D^A^@^E^@^C^@^D^@
..........
..........

Este fichero se compone de una cabecera, legible, seguida por los datos de impresión ya en el formato interno de la impresora. El lenguaje de impresión es XL2HB, que buscando un poco en internet resulta ser una versión de PCLXL hecha por  Brother.

La cabecera está también en un formato bastante popular llamado PJL, que es un lenguaje de control de trabajos de impresión. La parte interesante es esta:

@PJL SET RENDERMODE=COLOR

que es donde se define si el trabajo está en blanco y negro o en color. Esta claro que si justo antes de enviarlo a la impresora, hacemos una búsqueda en el fichero en bruto y cambiamos la línea anterior por:

@PJL SET RENDERMODE=GRAYSCALE

el trabajo llegará en blanco y negro, habiendo dado el cambiazo en el último momento. Esto se hace con una orden sed de sustitución ya archiconocida de Linux y Unix:

# sed "0,/@PJL SET RENDERMODE=COLOR/{s/@PJL SET RENDERMODE=COLOR/@PJL SET RENDERMODE=GRAYSCALE/}" < infile >outfile

Esta linea dice "búscame la primera aparición de @PJL SET RENDERMODE=COLOR y, si la hay, cámbiala por @PJL SET RENDERMODE=GRAYSCALE"

Al estar la orden dentro del fichero teacups.conf, en la clausula filter y en la sección de BN_SALAPROFESORES:

[BN_SALAPROFESORES]
filter : sed "0,/@PJL SET RENDERMODE=COLOR/{s/@PJL SET RENDERMODE=COLOR/@PJL SET RENDERMODE=GRAYSCALE/}"

se aplicará a todo trabajo de impresión que pase por la cola BN_SALAPROFESORES antes de enviarlo a la impresora, como era nuestro objetivo inicial.

Nota adicional: el formato XL2HB no está inicialmente soportado por pkpgcounter, por lo que da un número de páginas estrambótico al hacer la contabilidad. No problem, existe un parche sencillo sobre /usr/share/pyshared/pkpgpdls/pclxl.py (recordemos que es una variante de PCLXL) para que funcione:

Y con esto acabamos por esta vez.. Le estoy dando una caña tremenda al OpenWrt sobre los Astoria ARV7518PW, a ver si pronto tengo algo que contar, pero los Linux embebidos son duros...

 

miércoles, 15 de abril de 2015

Plugins rebeldes en Firefox y Chrome.

Adobe no es una empresa que se caracterice por su amor a Linux y a Firefox. Una de sus jugarretas ha sido dejar abandonada en la versión 11 el plugin de Flash para Firefox. La causa está en que Firefox utiliza el interface NPAPI para incluir plugins y Adobe decidió no actualizar mas el Flash con ese API en Linux. En cambio siguió actualizándolo para PPAPI, el API usado por Google Chrome, de tal forma que el Flash para Chrome ya va por la versión 17.
 
Afortunadamente, un máquina ruso decidió construir un wrapper de PPAPI a NPAPI que permite instalar el Flash Player diseñado para PPAPI en Firefox y otros navegadores basados en Mozilla, de tal forma que podamos disfrutar de versiones mas recientes de Flash en los mismos. Aquí van un par de enlaces con el software necesario:
Básicamente el truco está en descargarse el Pepper Flash Plugin, que es el que trae Chrome, copiarlo a la carpeta de plugins de Mozilla/Firefox e instalar o compilar el wrapper según las instrucciones de los enlaces.
 
Otro problema que tenemos son los plugins que ni siquiera existen en Linux,como el de Shockwave Player, también de Adobe, o el Silverlight de Microsoft. Pues tambien hay otra solución: Pipelight. Este es otro wrapper que permite instalar ambos plugins (y alguno mas, como el de Flash) dentro de Firefox, Chrome o Midori. Con Chromium parece ser que no funciona .
 
En este caso se usan los plugins originales de Windows mediante una versión tuneada de Wine que permite embeberlos dentro el navegador como un plugin más. Ojo al parche: no se instala el Firefox para Windows con los plugins dentro de Wine, lo que se hace es usar el Firefox de Linux y correr dentro los plugins usando un Wine customizado. Ahí es nada. 
 
Los enlaces son:
Básicamente hay que añadir su repositorio e instalar el paquete con el pipelight-plugin. Una vez hecho esto tan solo hay que activar los plugins. Según dicen en su página, soportan los siguientes plugin de Windows dentro de un Firefox para Linux:
  • Silverlight
  • Adobe Flash
  • Shockwave Player
  • Unity Web Player
  • Widevine
  • npactivex
  • Adobe Reader
  • Foxit PDF Reader
  • Grandstream Plugin
  • Hikvision Plugin
  • Roblox Plugin
  • Vizzed Retro Game Room
  • Viewright Caiway
  • Triangleplayer
  • Unity Web Player (64-bit)
  • Adobe Flash (64-bit)
Seguramente no sean infalibles y tengamos algún problema con ellos, pero no está mal esto de darle vueltas para hacer funcionar cosas a priori impensables. Cualquier día hacen funcionar el Autocad en Wine y yo me caigo de la silla.
 
 

jueves, 9 de abril de 2015

Obtener IP sabiendo la MAC

En una de las redes que administro casi todos los PC tienen IP dinámica. Cuando necesito conectarme a uno de ellos me encuentro con que no sé que IP tienen, aunque si que conozco su MAC. Una opción es usar nmap, pero normalmente es bastante lento ya que suele hacer mas cosas que una simple búsqueda de IP. Hace poco descubrí la utilidad arp-scan, ideal para mis fines y mucho mas rápida que nmap. La idea es hacer un script que dado un nombre de PC o bien una MAC, me averigüe su la IP que tiene en ese momento

Para ello, primero tenemos que hacer una lista de PC y MACs, y almacenarlos en un fichero "inventario.txt" con la estructura:

PC1=84:c9:b2:66:fa:c0
PC2=e8:61:94:26:3f:93

El script busca-ip.sh sería:

#!/bin/bash
#Esto debe ejecutarse como root

INTERFACE="eth0"
if [ "$EUID" -ne 0 ]
then
  echo "No eres root"
  exit 1
fi
if [ $# -eq 0 ]
then
   echo "Uso: $0"
   exit 1
fi
mac=$(grep -i "^$1=" inventario.txt | cut -d"=" -f2)
if [ -z $mac ]
then
   mac=$1
else
   echo "MAC: $mac"
fi
ip=$(arp-scan --interface=$INTERFACE --localnet | grep -i $mac)
if [ -z "$ip" ]
then
   echo "$1 no se ha encontrado"
else
   echo "La IP es $ip"
fi
exit 0

Para probar simplemente haremos (no olvidemos instalar previamente el paquete arp-scan):

# apt-get install arp-scan
# ./busca-ip.sh PC1
MAC: 84:c9:b2:66:fa:c0
La IP es 172.19.231.174 84:c9:b2:66:fa:c0       (Unknown)
# ./busca-ip.sh 84:c9:b2:66:fa:c0
La IP es 172.19.231.174 84:c9:b2:66:fa:c0       (Unknown)
# ./busca-ip.sh 84:c9:b2:66:fa:c1
84:c9:b2:66:fa:c1 no se ha encontrado

Y eso es todo por hoy.