Migración zero-touch de miles de extensiones desde Cisco CUCM hacia soluciones abiertas

Muy buenas !

En esta ocasión queremos compartir el know-how, experiencia y código que hemos adquirido estos últimos años en lo que a migraciones de entornos con varios miles de terminales conectados a Cisco Call Manager (CUCM) hacia soluciones abiertas y/o productos basados en SIP Standard.

Nos hemos querido centrar principalmente en los detalles importantes de los terminales de cara a plantear diseños con HA real y a tener controlado todo el detalle y flujo de provisión, más allá de lo que se encuentra habitualmente de «It works», dado que la documentación oficial con 3PCC de Cisco no es muy extensa, por no decir que es NULA. Hay muchos detalles que sólo las pruebas empíricas permiten conocerlos bien.

Personajes que necesitamos en el escenario

Como suele ser habitual, antes de entrar en detalle, nos gustaría exponer los elemento, entidades y conceptos clave que vamos a utilizar:

http://usecallmanager.nz/

El equipo detrás de UseCallmanager.nz se merecen nuestro más sincero agradecimiento, están detrás de:

  • Su propio portal: http://usecallmanager.nz, dónde hay documentación muy detallada, siendo la principal fuente de info de este post para todo lo relacionado con terminales. Se recomienda encarecidamente su lectura.
  • Los parches para Asterisk que publican en su web de cara a soportar features avanzadas con los terminales Cisco.

Asterisk/FreeSwitch

Nosotros nos centramos en esta ocasión en Asterisk, pero todo lo expuesto aquí realmente es válido para cualquier IP PBX / B2BUA / SIP Proxy standard. La excepción son los parches de UseCallManager, que no están disponibles para otras plataformas, pero al ser basados en SIP Standard, son relativamente fáciles de replicar usando un proxy previo que sea «programmatic».

Opensips/Kamailio

En nuestro caso, la arquitectura habitual es con varios proxys SIP, algunos con funciones de pseudo SBC y otros con funciones de mediación e integración: Transcoding, protocol changer, …

Entraremos luego en detalle.

Tools extra: Expect, Apache2, …

Estos terminales de cara a su control remoto tienen varios mecanismos vía JTAPI y SSH, utilizando herramientas standard como EXPECT, podremos automatizar varias de las acciones habituales y sobre todo de las críticas relacionadas con la ITL.

Entendiendo Cisco CUCM y sus terminales

¿Terminales compatibles con 3PCC(SIP)? ¿Chan SCCP – b, Chan SIP, Chan PJSIP?

Los terminales Cisco (79XX y nuevos modelos) son generalmente todos compatibles con lo que Cisco llama 3PCC, es decir, funcionar con SIP (teóricamente standard), así como con el protocolo SCCP.

Llegados a este punto, queremos ser claros con los caminos escogidos, ya que es posible que se quiera hacer un planteamiento diferente:

SCCP

SCCP (Skynny Client Control Protocol) es el protocolo habitual que utilizan estos terminales con Cisco CUCM. Si se quiere utilizar con alternativas libres no propietarias el único camino es Chan SCCP con Asterisk.

Al inicio de los tiempos, se consideraba un camino «técnicamente peor» que utilizar SIP, pero realmente con todo el push que le han dado a Chan SCCP , es una vía que sin duda hay que explorar. En nuestro caso, no nos decantamos por ella por temas de interoperabilidad con otros elementos SIP y poder llegar a tener (s)RTP End2End, sólo posible si todos los endpoints están con SIP.

Pero cabe destacar que Chan SCCP – b dispone de features muy avanzadas como:

  • Shared Lines reales (No SLA).
  • Call Quality Statistics. No es el standard report de SIP, pero con un poco de parseo es fácilmente integrable.

En definitiva, antes descartar la vía Chan SCCP – b, recomendamos mirarla con cariño. Los drawbacks principales son que te atas a un protocolo propietario, y por tanto sólo puedes funcionar con Chan SCCP B y que se rompe la interoperabilidad SIP, y eso en arquitecturas grandes dónde el media path es clave, es algo a tener muy en cuenta.

Chan PJSIP

Si hablamos de Asterisk, está claro que la vía SIP a coger es PJSIP, ya que en Asterisk 17 se observa:

chan_sip.c:35350 deprecation_notice: chan_sip has no official maintainer and is deprecated. Migration to
chan_sip.c:35351 deprecation_notice: chan_pjsip is recommended. See guides at the Asterisk Wiki:
chan_sip.c:35352 deprecation_notice: https://wiki.asterisk.org/wiki/display/AST/Configuring+res_pjsip
chan_sip.c:35353 deprecation_notice: https://wiki.asterisk.org/wiki/display/AST/Migrating+from+chan_sip+to+res_pjsip

Los terminales, al hablar SIP Standard, son perfectamente compatibles con el channel driver PJSIP, pero lo que si que es importante tener en cuenta:

  • Se pierden todas las features que aportan los patches de UseCallManager.nz

Chan SIP

La vía de Chan SIP es una vía con curva de puesta en marcha muy rápida, ya que tenemos todos los patches de useCallManager disponibles y Chan SIP lleva muchos años con nosotros. Es una pena que por el momento dichos patches no estén disponibles para PJSIP, ya que esta vía es por tanto una vía que se extinguirá en pocos años.

El camino de Chan SIP nos aporta como features adicionales gracias a los patches:

  • Sincronización de estado de terminal (DFKS – ECMA323) para la parte de desvíos y DND.
  • Notificación tipo BLF/Notify de From y To en llamadas a grupos.
  • Registro automático de líneas secundarias

Entre otras funcionalidades, esas serían las que destacan. Cabe destacar que todo esto se realiza utilizando SIP NOTIFY / SIP REFER.

Aplicar la inteligencia en el proxy previo

De cara a plantear un escenario que escale, una posible vía más que interesante, es la de utilizar las funcionalidades avanzadas de manejo de tráfico SIP que nos aportan soluciones como OpenSIPS y Kamailio para traspasar dicha inteligencia al extremo de acceso primario. Es decir, el primer punto / outboundproxy que utilizan los terminales Cisco.

Para estudiar esta vía hay que tener en cuenta estos comportamientos

  • Refer tras first register: Con los parches de UseCallamanger, se envían refers|notifys indicando el estado de sus configuraciones de desvíos. Esto sucede exactamente lo mismo cuando se cambia en el servidor, por lo tanto, se comporta como DFKS.

Concepto de ITL: La clave de todo el proceso

La ITL de los terminales Cisco es la Initial Trust List, y se comporta como un almacén de CA’s autorizadas en el terminal, gestionado todo de forma automática por Cisco CUCM.

Esto implica que el terminal:

  • Sólo se fiará a nivel de provisión (si está incluido en la ITL) de ficheros firmados por CA’s autorizadas.
  • Sólo se fiará a nivel de signalling de servidores SIP firmados por CA’s autorizadas.

Y estos puntos son determinantes, porque impiden una migración masiva de forma automática, es decir, por mucho que cambiemos las options 150&66 de los servidores DHCP, el terminal cuando pida provisión a los nuevos servidores, si no encuentra ficheros firmados, no se fiará de ellos.

Parece claro entonces: ¿Por qué no cogemos la CA completa con public and private key del Cisco CUCM y vivimos felices? Pues no, no es posible, Cisco no permite tal cosa, aunque si arrancamos en system rescue cd y tiramos del hilo, hay un path que parece viable. Sea como fuere, respuestas oficiales de Cisco:

¿Y cómo nos libramos de dicha ITL?

Factory reset (rompe el concepto zero-touch, inviable si tenemos miles de terminales)

Quitamos corriente, dejamos pulsado almohadilla y tecleamos 123456789*0#, eso genera un factory reset completo, con borrado de ITL incluido.

Pero claro, esto está bien para una emergencia o pruebas de laboratorio. No es viable hacerlo de forma automática en cientos, miles o decenas de miles de terminales. Salvo que tengamos un ejercito a nuestra disposición o usuarios muy sometidos jiji

APPs de 3eros: UPLINX

Este camino lo queremos exponer de primera, porque es una aplicación propietaria pero con un contexto muy asequible en proyectos de esta envergadura. Con la aplicación UPLINX PhoneControl (que realmente lo hace por JTAPI) podemos a golpe de click borrar la ITL, e incluso hacer trabajos tipo batch y borrar a X horas un grupo o grupos siguiendo X criterios.

Este es un camino que no permite ser automatizado de forma programática en un ciclo de vida del proceso completo, pero si vamos a ir migrando por ejemplo de 1000 en 1000 terminales en días controlados, con esta aplicación nos puede valer y de sobra. Se ejecuta sobre MS Windows, tiene versión desktop y web.

Igualmente, es interesante ver que dispone de un trial con lo que podemos probarla primero.

JTAPI

El camino de JTAPI permite integrar tanto el control de llamada como el control del terminal a nivel de pulsaciones mediante la API que aporta Cisco CUCM.

Dicho esto, a modo de ejemplo, este comando vía JTAPI:

ciscoterminal.sendData("<CiscoIPPhoneExecute><ExecuteItem URL='Key:Applications'/><ExecuteItem URL='Key:KeyPad0'/></CiscoIPPhoneExecute>");

Envía por ejemplo la tecla 0. En la página del proyecto hay varios ejemplos.

Previamente a lanzar JTAPI es importante resaltar que el application user que gestionemos tiene que tener permisos para manejar dicho endpoint, todo ello es fácilmente gestionable por AXL.

Un código completo de borrado de ITL por JTAPI sería el siguiente:

//package com.cisco.jtapi.senddata;

import javax.telephony.*;
import com.cisco.jtapi.extensions.*;

public class sendData {

  public static void main ( String [] args ) throws
  
    JtapiPeerUnavailableException, 
    ResourceUnavailableException, 
    MethodNotSupportedException, 
    InvalidArgumentException, 
    PrivilegeViolationException, 
    InvalidPartyException, 
    InvalidStateException, 
    InterruptedException {
      try{
        Handler handler = new Handler();


        if (args.length != 4) {
          System.out.println("Faltan argumentos: cucm, nombre_usuario, password, sep");
          System.exit(1);  
        }


        String cucm = args[0];
        String user_name = args[1];
        String password = args[2];
        String sep = args[3];

        // Create the JtapiPeer object, representing the JTAPI library
        System.out.println("Initializing Jtapi");
        JtapiPeer peer = JtapiPeerFactory.getJtapiPeer( null );
        
        // Create and open the Provider, representing a JTAPI connection to CUCM CTI Manager
        String providerString = String.format( "%s;login=%s;passwd=%s", cucm, user_name, password);
        
        System.out.println( "Connecting Provider: " + providerString );

        Provider provider = peer.getProvider( providerString );

        provider.addObserver( handler );
    
        // Wait for ProvInServiceEv
        System.out.println( "Awaiting ProvInServiceEv..." );

        handler.providerInService.waitTrue();

        String strSepNumber = sep;
        CiscoTerminal terminal = (CiscoTerminal) provider.getTerminal(strSepNumber);

        terminal.addObserver(handler);

        System.out.println( "Awaiting CiscoTermInServiceEv for: " + terminal.getName() + "...");
        /*
        System.out.println("Estado terminal:" + terminal.getDeviceState());
        System.out.println("Estado registro:" + terminal.getRegistrationState());
        */
        System.out.println("Tipo de tlf:" + terminal.getTypeName());

        handler.terminalInService.waitTrue(5);

        System.out.println( "<CiscoIPPhoneText><Text>Borrando tlv</Text></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneText><Text>Borrando tlv</Text></CiscoIPPhoneText>" ));
        Thread.sleep( 2000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Applications'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Applications'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 1000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Applications'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Applications'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 1000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad3'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad3'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 2000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad4'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad4'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 2000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad5'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad5'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 2000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad2'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPad2'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 2000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadPound'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadPound'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 500 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadStar'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadStar'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 500 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadStar'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadStar'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 500 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadPound'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:KeyPadPound'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 4000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Soft4'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Soft4'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 1000 );

        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Soft2'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem Priority='1' URL='Key:Soft2'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 1000 );
       
        System.out.println( "<CiscoIPPhoneExecute><ExecuteItem URL='Init:AppStatus'/></CiscoIPPhoneExecute>" );
        System.out.println( terminal.sendData( "<CiscoIPPhoneExecute><ExecuteItem URL='Init:AppStatus'/></CiscoIPPhoneExecute>" ));
        Thread.sleep( 1000 );

        System.exit( 0 );
        
      }catch (Exception e){
        e.printStackTrace();
        System.out.println(e.getMessage());
        System.exit(1);
      }
	}
}

SSH y técnica de pulsaciones de teclas

La vía de SSH es una vía «below the law», ya que no está pensado para ello, pero permite hacerlo.

A modo de ejemplo, el siguiente script de expect genera un reinicio del terminal:

# cat reiniciar.expect&nbsp;
set host [lindex $argv 0]
spawn ssh -oKexAlgorithms=diffie-hellman-group1-sha1 -oGlobalKnownHostsFile=/dev/null -oUserKnownHostsFile=/dev/null -c aes128-cbc cisco@$host
expect "*password"
sleep 2
send "XXXXX\n"
sleep 2
expect "login"
sleep 2
send "debug\n"
sleep 2
expect "password"
sleep 2
send "debug\n"
expect "*>"
sleep 2
send "test open\n"
expect "*Session"
sleep 2
send "test key set * * # * *\n"
sleep 5

Aquí la clave está en el paso de test open y test key. Esos comandos SSH lo que hacen es enviar pulsaciones al terminal. En este ejemplo se envia la tecla menú, luego * * # * * y eso genera un reinicio software del terminal (ojo, que no es un factory reset, ojála lo fuera jiji).

Con ello, podemos hacer las mismas pulsaciones que enviaríamos por JTAPI para ejecutar el borrado de la ITL. Basta con adaptar ese «send test key….» a las pulsaciones necesarias (atención al desbloqueo del menú).

Planteamiento de HA en Terminales Cisco 79XX

¿HA?

Si, es importante hablar de ello, porque como en todos estos diseños, los patrones de actuación se repiten siempre:

  • ¿Pasamos la inteligencia de la HA al SIP Endpoint?
  • ¿Disponemos de capacidad y opción de IP Virtual?
  • ¿Y si tenemos multi CPD sin L2 viable ni integración de nuestra parte con la inteligencia de red con EXA BGP o similar?

Esta claro que en un mundo ideal aplicaríamos patrones de diseño de arquitectura basados en NAPTR, SRV y viviríamos felices y contentos en nuestros estándares, pero os adelantamos que este no es el caso.

Conceptos base del comportamiento

Estos terminales Cisco se comportan así:

  • Permiten configurar uno, dos o varios proxys SIP
  • Se conectan a nivel TLS a todos los que hayamos definido
  • A nivel de Register SIP, sólo mantienen activo con uno de ellos, a los otros mandan un registers (expires=0).

Entrando en detalle, es importante ver su comportamiento:

Unregister al servidor que consideran inactivo

A continuación se muestra el unregister:

2020/07/30 10:27:44.489754 10.X:49408 -> 10.X:5061
REGISTER sip:10.X.155 SIP/2.0
Via: SIP/2.0/TLS 10.X.X.212:49408;branch=z9hG4bK7b31dafb
From: <sip:[email protected]>;tag=0026cbbe983f0529f831d9bf-47e37949
To: <sip:[email protected]>
Call-ID: [email protected]
Max-Forwards: 70
Date: Thu, 30 Jul 2020 08:27:44 GMT
CSeq: 588 REGISTER
User-Agent: Cisco-CP7911G/9.4.2
Contact: <sip:[email protected]:49408;user=phone;transport=tls>;+sip.instance="<urn:uuid:00000000-0000-0000-0000-0026cbbe983f>";+u.sip!devicename.ccm.cisco.com="SEP0
6CBBE983F";+u.sip!model.ccm.cisco.com="307";expires=0;cisco-keep-alive
Supported: replaces,join,sdp-anat,norefersub,resource-priority,extended-refer,X-cisco-callinfo,X-cisco-serviceuri,X-cisco-escapecodes,X-cisco-service-control,X-cisco-srtp-fa
back,X-cisco-monrec,X-cisco-config,X-cisco-sis-6.0.0,X-cisco-xsi-8.5.1
Content-Length: 0
Expires: 0

De este unregister, cabe destacar:

  • Cabecera expires a 0 (lo normal, dado que se quiere des registrar).
  • Cabecera cisco-keep-alive. Esta cabecera es interesante, ya que el terminal sólo la añade a sus proxys de respaldo, con lo cual, podemos decidir no gestionar ese register u otro tipo de decisiones de operativa base de proxys SIP.

Register activo

El register activo sería similar a este:

2020/07/30 15:40:13.016735 10.X.X.86:53133 -> 10.X.X.155:5061
REGISTER sip:10.X.X.155 SIP/2.0
Via: SIP/2.0/TLS 10.X.X.86:53133;branch=z9hG4bKd9c256e5
From: <sip:[email protected]>;tag=0026cbbf042d084e95eba647-e5c7d2f1
To: <sip:[email protected]>
Call-ID: [email protected]
Max-Forwards: 70
Date: Thu, 30 Jul 2020 13:40:13 GMT
CSeq: 4248 REGISTER
User-Agent: Cisco-CP7911G/9.4.2
Contact: <sip:[email protected]:53133;user=phone;transport=tls>;+sip.instance="<urn:uuid:00000000-0000-0000-0000-0026cbbf042d>";+u.sip!devicename.ccm.cisco.com="SEP00
CBBF042D";+u.sip!model.ccm.cisco.com="307"
Supported: replaces,join,sdp-anat,norefersub,resource-priority,extended-refer,X-cisco-callinfo,X-cisco-serviceuri,X-cisco-escapecodes,X-cisco-service-control,X-cisco-srtp-fa
back,X-cisco-monrec,X-cisco-config,X-cisco-sis-6.0.0,X-cisco-xsi-8.5.1
Content-Length: 0
Expires: 3600

Visto esto, es clave que tengamos en cuenta que el terminal se desregistra y registra sólo en uno, con lo cual, en nuestro diseño, tenemos que tener en cuenta todo esto muy bien, sobre todo si tenemos proxys por delante:

  • Si el proxy por delante está configurado para reenviar «directamente», ojo con las race conditions, se podría dar el caso de unregister’s.
  • Si hay varios proxys por delante, misma situación. Es decir, no se puede asumir que llegarán en orden o de alguna forma dichos ciclos de register/unregister.

¿Cuándo balancean? ¿Cuándo vuelven?

Tras probarlo N mil veces, confirmamos a ciencia cierta que los terminales deciden registrarse al siguiente proxy cuando:

  • Se corta la conexión TCP o TLS o no responde a nivel UDP a re-registers

En ese caso, al siguiente servidor SIP en orden de prioridad le mandan un register real. Al servidor con el que han perdido registro, volverán a intentarlo en 300 segundos, pero en este caso para mandar un unregister.

El terminal NUNCA volverá a su proxy principal, NUNCA. Una vez que conmuta se queda enganchado a este nuevo servidor.

Provisioning de la parte HA

Está todo ampliamente explicando en UseCallManager, pero las secciones clave del fichero SEP de configuración es la relativa a calLManagerGroup:

<callManagerGroup>
<members>
<member priority="1">
<callManager>
<ports>
<ethernetPhonePort>2000</ethernetPhonePort>
<sipPort>5060</sipPort>
<securedSipPort>5061</securedSipPort>
</ports>
<processNodeName>XXXX.local</processNodeName>
</callManager>
</member>
<member priority="0">
<callManager>
<ports>
<ethernetPhonePort>2000</ethernetPhonePort>
<sipPort>5060</sipPort>
<securedSipPort>5061</securedSipPort>
</ports>
<processNodeName>yyyyy.yyyy.local</processNodeName>
</callManager>
</member>
</members>
</callManagerGroup>

Sobre esto:

  • El terminal nada más bootear intenta primero tener como activo al member/(processnodeName) de menor priority.
  • Insistimos en que sólo es en boot-time, si luego falla el proxy, NADA le hará conmutar de nuevo.

Integración con proxy previo para HA

Sobre este punto, nosotros, como suele ser habitual en nuestras arquitecturas de voz, recomendamos poner siempre un proxy previo, y generalmente estamos optando por OpenSIPS, haciendo uso de MID Registrar, módulo del cual estamos eternamente agradecidos a sus developpers!

Con dicho módulo, lo que hacemos es poder disponer de un ciclo de register / re-register alto de cara a los terminales y dejar a los Application Server’s vivir tranquilos. Adicionalmente, cabe destacar que ganamos parapel forking (multi contact), lo cual no disponemos en Asterisk si vamos con Chan SIP, salvo dial múltiple, nunca tan limpio como un registro de la misma AOR en diferentes Contact’s.

Si se opta por este path, varias consideraciones clave con estos terminales:

tcp_max_msg_time=50

Los terminales, sobre todo en TLS, hay veces que tardan hasta 14-20 segundos en mandar tráfico de aplicación, con lo que OpenSIPS acaba cortando la conexión y no es tan sencillo de depurar, en la captura se ve un Alert y en el log, salvo debug alto no se ve nada muy muy explícito.

Por lo demás, el diseño puede seguir el modelo de arquitectura que más encaje en el diseño:

  • Actuar como mid_registrar con OpenSIPS y que sea un dropin (in the middle of the path con poco impacto)
  • Reenviar registers y no estar en en el medio o estarlo
  • No gestionar ni registers ni auth de ningún tipo en la parte IP PBX y tener siempre outboundproxy for all.

Sobre esto no nos queremos extender mucho, porque hemos hablado por aquí largo y tendido y se ha hablado también en numerosas ocasiones en Kamailio World, OpenSIPS Summit, VoIP2Day y eventos similares.

La batalla por la migración zero-touch con miles y miles de terminales

¿Qué es lo que buscamos?

Sobrevivir, poco impacto 😉 Básicamente en eso se resumen estos proyectos.

Ya mas en serio:

  • Convergencia completa desde la perspectiva del usuario: Que no sepa si ha sido migrado o si no.
  • Secuencia programable: Migrar lo que queremos en base a criterios técnicos, «políticos» o de otra índole. Es decir, que no sea por ejemplo el tercer octeto de la IP del terminal el punto que nos obligue a migrar en bloque 😉

Integración con PN compartido

Esto realmente no es mucha magia, ni nada hackie ni similar, básicamente, por poner un ejemplo.

  • Usuarios migrados desde solución CUCM a solución X: Se desvían a 777[su extensión] en CUCM
  • Usuarios no migrados: En la solución X están desviados de forma inicial a 777[su extensión]

y con algo tan simple y configurar un par de rutas en ambos entornos, ya nos permite ir migrando poco a poco.

Cuando decimos poco a poco no nos referimos a tener una hoja de excel gigante donde apuntamos lo migrado y un ejercito de clones haciéndolo 😉 Todo esto es gestionable con la API AXL de Cisco CUCM

Con lo que puede estar claramente dentro del ciclo del automatismo completo que estamos diseñando junto con el punto del DHCP selectivo que describimos a continuación.

Y, lo mas importante, todo esto es gestionable de forma batch, con comprobaciones atómicas que hagan en pasadas de loop para comprobar que todo va OK, asi no nos dejamos ningún muerto por el camino, o si cae alguno, que sean los mínimos 😉

APP de migración paulatina: DHCP MiTM, DHCP inteligente, TFTP MiTM

Antes de entrar en detalle de esto es importante conocer el proceso de boot y provision:

  1. El terminal nada más arrancar obtiene IP por dhcp (una nueva o el mismo lease).
  2. En dicho lease está o bien la option 150 o la option 66 (TFTP).
  3. El terminal pide provisión primero por HTTP al puerto 6970 de lo que haya recibido en dichas opciones, y si no está disponible por HTTP lo intenta por TFTP

Como en todo, es mas trazable por HTTP y mejor protocolo que TFTP, con lo que recomendamos esa vía. Ya que se tenemos control de ejecución en el lado del servidor de forma fácil con cualquier rewrite rule o similar

Al pedir provisión ya entramos en todo el mar de tener que tener provisión firmada y que el terminal se fie de nosotros o que no tenga ITL cargada. Así que conviene pararse un momento a analizar en que punto estamos:

Si queremos que se provisione tenemos por tanto que cambiar el TFTP, y por tanto, vamos afectar a todos!

Si, se afectará a todos, salvo que haya muchas vlans diferenciadas, y con arquitectura dhcp relay server o similar o sin ella, se puede hacer cambio mas directo

¿Entonces cómo hacemos para poder hacerlo selectivo en base a grupos de terminales de dpto o incluso de un modelo concreto?

Si no queremos entrar en complicaciones y es viable un cambio de VLAN, el camino seguido por la Universidad de Murcia y comentado en las charlas del VoIp2Day puede encajar. En dicha aproximación se opta por cambiar el puerto de la electrónica de red (mediante expect también), para que por tanto anuncie por CDP otra vlan de voz y por tanto el terminal coja IP de otro rango y disponga de un option66/150 diferente. Esto puede ser viable si el impacto de añadir una nueva vlan o la cantidad de puertos y uniformidad de tecnologías permite este path.

Llegados a este punto y si asumimos que no queremos o no podemos cambiar de VLAN, aquí es dónde habría que aplicar cierto desarrollo (recordemos que si se acepta migrar de golpe o por red completa, esto no haría falta), como por ejemplo esta integración con un DHCP server selectivo que hemos desarrollado en python:


Este código no lo tenemos publicado en Github, al contrario que prácticamente todo lo que hacemos, principalmente porque no lo tenemos «puesto bonito» y documentado, pero no tenemos problema en compartirlo si se quiere.

Básicamente, esta aplicación lo que hace es tener packs de migración en base a criterios de la propia BBDD de Cisco CUCM (device pool, departamento, descripción, ip, directory number, …).

Conclusión y cierre de filas 😉

Nada más por aquí, como conclusión simplemente comentaros que las migraciones de miles, decenas de miles de extensiones desde entornos Cisco CUCM hacia entornos SIP Standard son totalmente viables y con impacto mínimo para el usuario. Son y deben ser progresivas, en base a criterios que se marquen, migrar departamentos, edificios, rangos de extensiones, servicios o lo que se decida migrar, de forma paulatina.

Cisco CUCM mediante JTAPI y AXL nos permite este tipo de movimientos, y los terminales con firmware SIP soportan correctamente provisión segura, SIP TLS y SRTP, con lo que el destino del viaje es más que correcto.

Un saludo a todo el mundo y cualquier cosa, estamos por aquí !

 



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Gorka Gorrotxategi

CTO en Irontec, en el frente técnico desde un par de lustros ya, para todo lo que tenga que ver con Networking, VoIP y Sistemas, en ese orden :D) Desde @zetagor escribo algo, pero poco verbose la verdad

Queremos tu opinión :)