Mi Brain-Training Personal

Para que no se me olviden las cosas…

Probando el framework de comunicaciones Plivo

Plivo es un framework de comunicaciones creado alrededor de FreeSWITCH con la intención de proporcionar un entorno en el que desarrollar aplicaciones multimedia. Puede compararse con Twilio, pero con una importante diferencia: es Open Source.

Para hacernos una idea de la arquitectura del sistema completo podemos consultar la sección overview de su página web. Aquí una de las imágenes a modo resumen:

Ya que Plivo es una solución hosted, es decir, tenemos que instalar nuestro propio servidor, tendremos que hacer algo de trabajo antes de poder probar una aplicación hola-mundo, pero afortunadamente Plivo viene con unos cuantos scripts que nos harán la vida muy fácil. ¡Al tema!

Paso 1: Instalar FreeSWITCH

Partiremos de una Debian Squeeze recién instalada y utilizaremos el script de instalación provisto por Plivo. La razón es que necesitamos una configuración específica para trabajar con Plivo y el script ya lo hace todo solo ;-) Podemos consultar algo más de documentación en la sección get started.

# wget --no-check-certificate https://github.com/plivo/plivo/raw/master/freeswitch/install.sh
# chmod +x install.sh
# ./install.sh

Una vez tenemos FreeSWITCH instalado (estará en /usr/lcoal/freeswitch/) podemos arrancarlo en background ejecutando:

# ./usr/local/freeswitch/bin/freeswitch -nc

Y podemos conectarnos a un CLI interactivo así:

# ./usr/local/freeswitch/bin/fs_cli

Paso 2: Instalar Plivo

Para instalar Plivo también utilizaremos un script y lo instalaremos en /opt/plivo/:

# wget --no-check-certificate https://github.com/plivo/plivo/raw/master/scripts/plivo_install_beta.sh
# chmod +x plivo_install_beta.sh
# ./plivo_install_beta.sh

Paso 3: Configurar Plivo

Una vez tenemos instalados FreeSWITCH y Plivo vamos a modificar ligeramente la condifuración de Plivo. Por defecto escucha peticiones del API REST en 127.0.0.1 (no Mike, creo que aún no soporta IPv6) así que lo cambiaremos para poder desarrollar en otra máquina.

Editamos el fichero /opt/plivo/etc/plivo/default.conf y configuramos la IP de la máquina donde tendremos nuestra aplicación escuchando:

...
DEFAULT_ANSWER_URL = http://192.168.99.53:5000/answered/
DEFAULT_HANGUP_URL = http://192.168.99.53:5000/hangup/
...
ALLOWED_IPS = 127.0.0.1,192.168.99.53
...
HTTP_ADDRESS = 0.0.0.0:8088
...
CALL_HEARTBEAT_URL = http://192.168.99.53:5000/heartbeat/

Ya estamos listos para arrancar Plivo:

/opt/plivo/bin/plivo start

Paso 4: Hola mundo

Ya tenemos todo listo ahora veámoslo en acción. El ejemplo a continuación está hecho echo en Python con Flask (un framework web) y hará que al llamar a sip:cualquiercosa@IP_de_nuestro_servidor:5080 se escuche “hello world” utilizando Text-To-Speech.

############################################
# host/port binding for http server
HOST = '0.0.0.0'
PORT = 5000
############################################

from flask import Flask, render_template
import plivohelper
import os


response_server = Flask("ResponseServer")
response_server.debug = True

"""
This is a simple example which demonstrate how easy you can build a light HTTP
server using Flask which will return formatted XML to command the Plivo Server

By default the HTTP Server will be listening on http://127.0.0.1:5000

The following URLs are implemented:
* /answered/
* /hangup/
"""


@response_server.route('/heartbeat/', methods=['POST'])
def heartbeat():
    return "OK"

@response_server.route('/hangup/', methods=['POST'])
def hangup():
    return "OK"

@response_server.route('/answered/', methods=['POST'])
def answered():
    r = plivohelper.Response()
    r.addSpeak("Hello world")

    print "RESTXML Response => %s" % r
    return render_template('response_template.xml', response=r)


if __name__ == '__main__':
    if not os.path.isfile("templates/response_template.xml"):
        print "Error : Can't find the XML template : templates/response_template.xml"
    else:
        response_server.run(host=HOST, port=PORT)

view raw helloworld.py This Gist brought to you by GitHub.

Esto tiene pinta de que me va a tener muy entretenido, ya iré posteando algo más a medida que lo vaya conociendo mejor.

Mandando comandos a OpenSIPS con mi_datagram

OpenSIPS dispone de diversos módulos mediante los cuales podemos enviar comandos para realizar determinadas acciones o consultar información. Podemos interactuar con OpenSIPS mediante un socket (mi_datagram) un fifo (mi_fifo) o XML-RPC (mi_xmlrpc).

Para mandar comandos sencillos podemos utilizar la herramienta de consola que ya viene con OpenSIPS opensipsctl, pero el fifo al que nos conectamos viene dado por el valor almacenado en un fichero (por defecto en /etc/opensips/opensipsctlrc), por lo tanto no es muy flexible si tenemos varias instancias de OpenSIPS corriendo en la misma máquina.

En MediaProxy y CallControl hacemos uso del módulo mi_datagram, para comunicarnos con OpenSIPS e indicarle que ha de terminar un diálogo, por ejemplo, por lo que pensé en reutilizar ese código para poder mandar comandos más fácilmente desde una interfaz de consola.

El resultado:

# coding=utf-8

# Copyright (C) 2011 Saúl Ibarra Corretgé <saghul@gmail.com>
#

# Based on callcontrol/opensips.py, Copyright (C) 2006-2011 AG Projects.

# OpenSIPS configuration example:
# loadmodule "mi_datagram.so"
# modparam("mi_datagram", "socket_name", "/var/run/opensips/socket")
# modparam("mi_datagram", "unix_socket_user", "opensips")
# modparam("mi_datagram", "unix_socket_group", "opensips")


import os
import socket

from application.process import process
from application.system import unlink
from itertools import count

from twisted.internet import reactor, defer, stdio
from twisted.internet.protocol import DatagramProtocol
from twisted.internet.error import CannotListenError
from twisted.protocols.basic import LineReceiver
from twisted.python.failure import Failure



class Error(Exception): pass
class CommandError(Error): pass
class TimeoutError(Error): pass
class NegativeReplyError(Error): pass


class Request(object):
    def __init__(self, command):
        self.command = command
        self.deferred = defer.Deferred()


class UNIXSocketProtocol(DatagramProtocol):
    noisy = False

    def datagramReceived(self, data, address):
        deferred = self.transport.deferred
        if deferred is None or deferred.called:
            return
        # accumulate in a buffer until message end (do this later when implemented by opensips) -Dan
        if not data:
            failure = Failure(CommandError("Empty reply from OpenSIPS"))
            deferred.errback(failure)
            return
        try:
            status, msg = data.split('\n', 1)
        except ValueError:
            failure = Failure(CommandError("Missing line terminator after status line in OpenSIPS reply"))
            deferred.errback(failure)
            return
        if status.upper() == '200 OK':
            deferred.callback(msg)
        else:
            deferred.errback(Failure(NegativeReplyError(status)))


class UNIXSocketConnection(object):
    timeout = 3

    def __init__(self, opensips_socket_path):
        self._initialized = False
        self.opensips_socket_path = opensips_socket_path
        self.path, self.transport = self.initialize_local_socket()
        reactor.addSystemEventTrigger('during', 'shutdown', self.close)
        self.transport.deferred = None ## placeholder for the deferred used by a request
        self._initialized = True

    def initialize_local_socket(self):
        counter = count()
        while True:
            i = counter.next()
            socket_name = "opensips_%02d.sock" % i
            socket_path = process.runtime_file(socket_name)
            if os.path.exists(socket_path):
                continue
            try:
                transport = reactor.listenUNIXDatagram(socket_path, UNIXSocketProtocol())
            except CannotListenError:
                pass
            else:
                return socket_path, transport

    def close(self):
        if self._initialized:
            self.transport.stopListening()
            unlink(self.path)

    def _get_deferred(self):
        return self.transport.deferred
    def _set_deferred(self, d):
        self.transport.deferred = d
    deferred = property(_get_deferred, _set_deferred)

    def _did_timeout(self, deferred):
        if deferred.called:
            return
        deferred.errback(Failure(TimeoutError("OpenSIPS command did timeout")))

    def send(self, request):
        self.deferred = request.deferred
        try:
            self.transport.write(request.command, self.opensips_socket_path)
        except socket.error, e:
            self.deferred.errback(Failure(CommandError("Cannot send request to OpenSIPS: %s" % e)))
        else:
            reactor.callLater(self.timeout, self._did_timeout, self.deferred)


class ManagementInterface(object):

    def __init__(self, opensips_socket_path):
        self.conn = UNIXSocketConnection(opensips_socket_path)

    def _RH_command(self, result):
        if isinstance(result, Failure):
            print "failed to execute command: %s" % result.value

    def send_command(self, command, callback=None):
        _parts = command.split()
        cmd, rest = _parts[0], _parts[1:]
        cmd = ':%s:\n%s\n' % (cmd, '\n'.join(rest))
        request = Request(cmd)
        request.deferred.addBoth(callback or self._RH_command)
        self.conn.send(request)


class StandardIOProtocol(LineReceiver):
    delimiter = os.linesep

    def __init__(self, opensips_socket):
        self.opensips_socket = opensips_socket

    def connectionMade(self):
        self.management = ManagementInterface(self.opensips_socket)
        self.transport.write('>>> ')

    def lineReceived(self, line):
        if line:
            self.management.send_command(line, self._command_cb)

    def _command_cb(self, result):
        if isinstance(result, Failure):
            data = "failed to execute command: %s" % result.value
        else:
            data = result
        self.transport.write(data+self.delimiter)
        self.transport.write('>>> ')


if __name__ == '__main__':
    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option('--socket', dest='socket_file', default='/var/run/opensips/socket', metavar='File')
    options, args = parser.parse_args()

    stdio.StandardIO(StandardIOProtocol(options.socket_file))
    reactor.run()

Un script que toma el comando que le pasemos por la línea de comando, lo formatea correctamente y lo envía al socket de OpenSIPS. Es bastante simple, pero nunca había probado el módulo stdio de Twisted, que permite hacer IO asíncrona de la entarda estándar, y ésta era una buena ocasión para probarlo. ;-)

SIP más allá de la VoIP

Como ya comenté en otro post, esta semana pasada tuvieron lugar las jornadas Freedom For Hardware and Communications (f4hc) organizadas por Itsas.

Estuve por allí los 2 dias y desde aquí quiero felicitar a la organización por el buen trabajo realizado. Todo impecable y con un repertorio más que interesante de ponentes. Me gustó especialmente la inclusión del hardware libre como temática, ya que es algo que no se ve habitualmente.

Lo dicho, mi enhorabuena a la organización, espero que el año que viene el evento se repita ¡y se supere!

A continuación os dejo las transparencias de la charla que tuve la ocasion de dar: “SIP mas alla de la VoIP”.

Enjoy!



Jornadas f4hc: Freedom For Hardware and Communications

Los próximos 14 y 15 de julio tendrán lugar unas jornadas dedicadas a las comunicaciones libres y el hardware libre, f4hc, que se celebrarán el el Paraninfo Bzkaia Aretoa de la UPV/EHU.

Me dieron la oportunidad de participar, por lo cual estoy muy agradecido, y daré una charla titulada “SIP más allá de la VoIP” en la que mostraré que SIP vale para más cosas que para hacer una llamada de voz y video.

El programa tiene muy buena pinta, con charlas muy interesantes tanto sobre proyectos software como hardware. Si estás por la zona no dudes en incribirte.

¡Allí nos vemos!

Vía el blog de Dani

Skype lanza SkypeKit, un SDK para desarrolladores

Skype ha lanzado SkipeKit, un SDK para permitir a desarrolladores de terceros integrar sus aplicaciones con Skype. El acceso al SDK cuesta 10$ (un solo pago) y según parece dispone de bindings en Java y C++.

Todavía no tengo muy claro como afecta esto a SIP, más bien parece un movimiento contra Google y su Jingle / WebRTC. Si posibilitan la integración de otras aplicaciones con Skype conseguirán que más gente necesite utilizarlo. Algo nada interesante para los estándares abiertos.

Una vez más, ya veremos cómo acaba esto. De mientras, y para los curiosos, podéis echar un vistazo al API aquí:

A juzgar por el Confidential/Proprietary que aparece en el pie de la página de C++ parece que se hayan dejado un link sin proteger :-) Hay hasta ejemplos.

Google pasa a usar Jingle estándar

En un email enviado a la lista de correo de Jingle, se ha anunciado que a partir de ahora Google pasará a utilizar Jingle estándar (y no una variante, como venían haciendo) en Google Talk, iGoogle, Orkut y Android.

Aquí el anuncio oficial:

We are pleased to announce that we have launched support for Jingle
XEP-166 and XEP-167 for Google Talk calls to and from Gmail, iGoogle,
and Orkut. We have also added the same level of support to libjingle
(http://code.google.com/p/libjingle), which is used by many native
clients. From this point on, it will be our primary signalling
protocol, and the old protocol will only remain for backwards
compatibility. We also plan to soon update Google Talk on Android to
speak Jingle, but we do not plan on updating the Google Talk Windows
application.

We suggest all clients that interop with Google Talk to switch to
using Jingle rather than the old protocol. We will remain backwards
compatible with legacy clients by continuing to speak the old protocol
as well. If you wish to continue working with legacy clients, such as
the Google Talk application for Windows, you may also wish to continue
speaking the old protocol. But the future is Jingle, and the old
protocol will eventually go away.

Finally, we are still working on implementing XEP-176 (ICE-UDP). In
the meantime, you’ll need to use our draft-06 version of ICE, which is
implemented both in libjingle and in libnice, two open source
libraries.

I hope that this will be a support to the Jingle community and futher
our efforts to have open standards for voice and video communication.

- Peter Thatcher

Hace no mucho que Google liberó el project WebRTC, y parece que sus intenciones son claras y que apuestan por Jingle y la VoIP desde el navegador.

Veremos cómo evoluciona el asunto, pero en cualquier caso, es una buena noticia para los estándares abiertos. :-)

Asterisk ya habla Euskera

Hoy Igor ha anunciado en Twitter la disponibilidad de los ficheros de audio para la traducción de Asterisk al Euskera. Hace algún tiempo que me lo comentó y lamenté no haber podido a la presentación del Proyecto Fin de Carrera, es lo que tienen 1400 Kms de distancia :-S

La traducción de los sonidos al Euskera ha debido de ser una tarea bastante ardua, son unos cuantos ficheros:

>>> import os
>>> from itertools import chain
>>> print len([file for file in chain(*(filenames for dirpath, dirnames, filenames in os.walk('.'))) if file.endswith('.wav')])
>>> 491

Además, han creado 2 juegos de sonidos, con voz masculina y femenina. Podéis descargaros ambos desde aquí.

Desde aquí mi enhorabuena al equipo formado por Mikel Arribillaga, Jon Echezortu y Maria Epelde, dirigidos por Igor. ¡Muchas gracias por realizar el trabajo y compartirlo bajo la licencia GPL!

Esto puede ser muy interesante a la hora de presentar soluciones basadas en Asterisk para instituciones públicas en Euskadi. :-)

Si queréis ver una muestra, aquí tenéis el mítico demo-congrats.

Pequeñas mejoras (o eso espero)

¡A los buenos días!

Hoy por fin he encontrado esos 20 minutos (y las ganas) que necesitaba para actualizar la versión de WordPress, algunos plugins y demás historias del blog. Y la verdad es que estoy bastante cansado de hacer eso cada X tiempo, cuando X tiende a “no mucho”. Es por esto que planeo abandonar WordPress por un sistema de generación de webs estáticas como Jekyll o Hyde. Lo más probable es que acabe utilizando Hyde, ya que está escrito en Python y al menos puedo entenderlo y/o escribir algún plugin que necesite. :-)

La idea es poder escribir los posts con el ViM utilizando Markdown y mantener el blog en un repositorio de git en GitHub.

Lo que a éste tipo de sistemas les suele faltar es un sistema de comentarios. Pero por suerte tenemos Disqus. Disqus es un sistema externo de comentarios que permite login con Facebook, Twitter, OpenID, etc. y que es posible integrar en prácticamente cualquier sistema de blogging. Con la idea de abandonar WordPress en algún momento, he instalado el plugin de Disqus aquí, de manera que los comentarios que dejéis a partir de ahora serán con Disqus. Lo mejor de todo es que Disqus se sincroniza con la base de datos local de WordPress, por lo que si cambio de idea todos los comentarios seguirán disponibles en la base de datos local. Awesomness^3.

El segundo cambio es transparente y es posible que ya lo hayáis utilizado sin daros cuenta: desde el pasado IPv6 day, saghul.net dispone tanto de regitro DNS de tipo A como AAAA, así que podríamos decir que está IPv6 enabled. Crear un dominio ipv6.dominio.com o www6.dominio.com para una web con WordPress es bastante inútil por lo que comprobé, ya que sólo verás la primera página a través de IPv6, todos los links que WordPress ya tiene generados apuntarían a un dominio que no tiene registro AAAA y por lo tanto se utilizaría IPv4.

IPv6 por toda la casa

Hace un par de meses que no escribo nada por aquí, así que con la excusa del IPv6-day me he animado a escribir un post que hace tiempo tenía pensado escribir.

Vamos a ver cómo conectar no solo un ordenador, sino toda la casa al mundo IPv6. Para ello vamos a suponer que no tenemos IPv6 de manera nativa, pero que disponemos de un servidor en algún datacenter (también sin IPv6 de manera nativa) y un router con GNU/Linux. También vamos a suponer que algo sabemos de OpenVPN y Quagga.

Para llevar a cabo esta frikada he contado con la inestimable ayuda de Mikel “Mike” Jiménez, un yonki de las redes que vive por y para el TCP/IP y el único al que dejaría una shell de root en mi router sin preocuparme. :-)

Dado que no disponemos de conectividad real IPv6 necesitaremos los servicios de un broker que nos provea de un túnel. Para este montaje utilizaremos el servicio proporcionado por Hurricane. Para poder crear el túnel necesitamos que nuestro extremo disponga de una IP fija, pero como vamos a configurarlo en el host que tenemos en un datacenter esto no debería suponer un problema.

Al finalizar la solicitud través de la web obtendremos un rango /64 para nosotros. Suficiente, ¿no? Pues no. Hemos dicho que queremos tener IPv6 en toda la casa, e IPv6 nos ofrece un mecanismo para asignar una dirección IP a cada dispositivo sin la necesidad de utilizar DHCP: Router Advertisement. Básicamente nuestro router tendrá una IP en este rango y se anunciará mediante paquetes RA y los dispositivos generarán una IP única en ese rango utilizando su MAC. Esto significa que necesitamos un rango más grande si queremos crear varias subredes /64 para poder interconectar a varios colegas con IPv6 mediante un único túnel. Hurricane nos da la posibilidad de obtener un rango /48, así que no hay problema :-)

A continuación podéis ver una captura de pantalla de los datos de la red IPv6 /64 que Hurricane nos ha dado y la /48 que nos enruta a través de ella:

Ya que hemos dicho que teníamos un servidor en un datacenter, lo utilizaremos para crear múltiples subredes IPv6 que saldrán al mundo real por un único túnel a través de Hurricane. Veamos una imagen con la estructura que queremos montar:

Como se aprecia en la imagen, todos los nodos (los routers de nuestras casas) están conectados mediante una VPN sobre IPv4 al servidor. Utilizaremos una subred que actuará como red de tránsito entre nuestro server y cada una de las subredes de los clientes que conectemos.  Los clientes se conectarán mediante una VPN sobre IPv4. En éste túnel, implementaremos tanto IPv4 como IPv6, teniendo así soporte dual stack en la VPN. Para publicar la red que nos ha sido asignada como cliente, usaremos OSPFv3. ¡Vamos al lio!

Primero configuramos el tunel con Hurricane tal y como se nos indica en la web:

ip tunnel add he-ipv6 mode sit remote 216.66.84.42 local 91.121.117.27 ttl 255
ip link set he-ipv6 up
ip addr add 2001:470:1f12:286::2/64 dev he-ipv6
ip route add ::/0 dev he-ipv6
ip -f inet6 addr

Es una buena idea poner esto en un script y ejecutarlo en el post-up de la interfaz a través de la cual tiraremos el túnel, para no tener que lanzarlo manualmente al reiniciar.

Una vez tenemos el túnel levantado ya deberíamos poder hacer ping6 a ipv6.google.com y molar. Ahora elegiremos una subred (de la /48 que nos enrutan) que usaremos como red de tránsito. En nuestro caso, nos enrutan la siguiente subred:

2001:470:c846::/48

Y elegiremos la siguiente subred de transito:

2001:470:c846:1::/64

En el extremo de la VPN del servidor, nos pondremos la primera IP de la subred de transito, y cada cliente podrá elegir cualquier IP en su extremo. Con 2^64 posibilidades creo que no habrá muchas colisiones ;-)

2001:470:c846:1::1/64

Veamos la configuración de Quagga para el servidor:

interface tap0
 ipv6 address 2001:470:c846:1::1/64
 ipv6 nd suppress-ra
 ipv6 ospf6 cost 1
 ipv6 ospf6 dead-interval 40
 ipv6 ospf6 hello-interval 10
 ipv6 ospf6 instance-id 0
 ipv6 ospf6 priority 255
 ipv6 ospf6 retransmit-interval 5
 ipv6 ospf6 transmit-delay 1
 link-detect
!
router ospf6
 router-id 2.2.2.2
 redistribute kernel
 redistribute static
 interface tap0 area 0.0.0.0

Lo importante de todas opciones arriba listadas son:

  • IPv6 address: indica la IP del interfaz tap0
  • Router-id, nos podemos inventar
  • Area, necesiaremos que sea el mismo en el servidor y los clientes
  • Redistribute kernel: hará que se publique la IP del servidor por OSPF para que los clientes la usen como ruta por defecto para el tráfico IPv6

Ahora veamos la configuración en un cliente:

interface eth1
 ipv6 address 2001:470:c846:2::1/64
 ipv6 nd prefix 2001:470:c846:2::/64
 ipv6 nd ra-interval 30
 ipv6 nd ra-lifetime 600
 link-detect
 no ipv6 nd suppress-ra
!
interface tap0
 ipv6 address 2001:470:c846:1::666/64
 ipv6 nd suppress-ra
 ipv6 ospf6 cost 1
 ipv6 ospf6 dead-interval 40
 ipv6 ospf6 hello-interval 10
 ipv6 ospf6 instance-id 0
 ipv6 ospf6 priority 1
 ipv6 ospf6 retransmit-interval 5
 ipv6 ospf6 transmit-delay 1
 link-detect
!
router ospf6
 router-id 1.1.1.1
 redistribute connected
 interface tap0 area 0.0.0.0

Aquí tenemos varias cosas. Por un lado, tenemos que elegir una subred (al azar, la posibilidad de colisión es casi nula) que será la que usemos en ese cliente (nuestara casa):

  • IPv6 address (eth1): indica la IP que tendrá este router en la subred que hemos seleccionado para este cliente
  • IPv6 nd prefix (eth1): indica el prefijo por el cual se hará el neighbour discovery
  • “no ipv6 nd suppress-ra”: indica que se utilizará Router Advertisement para que los dispositivos que tengamos por casa de autoprovisionen. Podríamos haber utilizado radvd, pero ya que se usa Quagga para el OSPF…
  • IPv6 address (tap0): indica la IP del extremo del túnel del lado del cliente. Ha de pertenecer a la red de tránsito que hemos definido arriba.
  • Redistribute connected: indica que se publicarán rutas para llegar hasta ésta subred por OSPF. De esta manera el servidor sabrá cómo enrutar los paquetes que tengan como destino la subred que hemos elegido y habremos habilitado el enrutamiento entre clientes/casas de frikis y también desde/hacia Internet.

Los que entendáis algo del asunto estaréis pensando que hemos matado moscas a cañonazos. Y tenéis razón. ¿Entonces porqué hacerlo? Porque podemos.

Happy routing!

Google utiliza OpenSIPS y YATE en Google Voice

A ver si al final va Google y permite usar SIP con Google Voice… aunque sea con números de teléfono.

Resulta que si sabemos el número de GV de un usuario le podemos ayer le podíamos llamar de manera gratuita por SIP de la siguiente manera:

sip:+14049397606@sip.voice.google.com (el de ejemplo es mi número)

Al comprobar que esto era cierto, lo primero que se me ocurrió fue hacer una consulta DNS y ver si tenían registros DNS SRV:

saghul@hal:~$ dig srv _sip._udp.sip.voice.google.com

; <<>> DiG 9.7.2-P3 <<>> srv _sip._udp.sip.voice.google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45638
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 13, ADDITIONAL: 0

;; QUESTION SECTION:
;_sip._udp.sip.voice.google.com.        IN      SRV

;; ANSWER SECTION:
_sip._udp.sip.voice.google.com. 82958 IN SRV    40 1 5060 alt3.voice-sip.l.google.com.
_sip._udp.sip.voice.google.com. 82958 IN SRV    50 1 5060 alt4.voice-sip.l.google.com.
_sip._udp.sip.voice.google.com. 82958 IN SRV    10 1 5060 voice-sip.l.google.com.
_sip._udp.sip.voice.google.com. 82958 IN SRV    20 1 5060 alt1.voice-sip.l.google.com.
_sip._udp.sip.voice.google.com. 82958 IN SRV    30 1 5060 alt2.voice-sip.l.google.com.

;; AUTHORITY SECTION:
com.                    83093   IN      NS      d.gtld-servers.net.
com.                    83093   IN      NS      i.gtld-servers.net.
com.                    83093   IN      NS      c.gtld-servers.net.
com.                    83093   IN      NS      a.gtld-servers.net.
com.                    83093   IN      NS      j.gtld-servers.net.
com.                    83093   IN      NS      e.gtld-servers.net.
com.                    83093   IN      NS      k.gtld-servers.net.
com.                    83093   IN      NS      m.gtld-servers.net.
com.                    83093   IN      NS      l.gtld-servers.net.
com.                    83093   IN      NS      b.gtld-servers.net.
com.                    83093   IN      NS      h.gtld-servers.net.
com.                    83093   IN      NS      f.gtld-servers.net.
com.                    83093   IN      NS      g.gtld-servers.net.

;; Query time: 0 msec
;; SERVER: 192.168.99.116#53(192.168.99.116)
;; WHEN: Mon Mar  7 22:45:44 2011
;; MSG SIZE  rcvd: 502

Parece que si, ¡bien! Ahora probemos a mandarle un OPTIONS:

saghul@hal:~$ sipsak -vvv -s sip:test@sip.voice.google.com
No SRV record: _sip._tcp.sip.voice.google.com
using SRV record: _sip._udp.sip.voice.google.com:5060
fqdnhostname: 192.168.99.53
warning: need raw socket (root privileges) to receive all ICMP errors
our Via-Line: Via: SIP/2.0/UDP 192.168.99.53:53335;branch=z9hG4bK.40128570;rport;alias

New message with Via-Line:
OPTIONS sip:test@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:53335;branch=z9hG4bK.40128570;rport;alias
From: sip:sipsak@192.168.99.53:53335;tag=626043f9
To: sip:test@sip.voice.google.com
Call-ID: 1650476025@192.168.99.53
CSeq: 1 OPTIONS
Contact: sip:sipsak@192.168.99.53:53335
Content-Length: 0
Max-Forwards: 70
User-Agent: sipsak 0.9.6
Accept: text/plain

request:
OPTIONS sip:test@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:53335;branch=z9hG4bK.40128570;rport;alias
From: sip:sipsak@192.168.99.53:53335;tag=626043f9
To: sip:test@sip.voice.google.com
Call-ID: 1650476025@192.168.99.53
CSeq: 1 OPTIONS
Contact: sip:sipsak@192.168.99.53:53335
Content-Length: 0
Max-Forwards: 70
User-Agent: sipsak 0.9.6
Accept: text/plain

send to: UDP:74.125.95.192:5060

message received
received from: UDP:74.125.95.192:5060
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.99.53:53335;branch=z9hG4bK.40128570;rport=64017;alias;received=62.131.6.55
From: sip:sipsak@192.168.99.53:53335;tag=626043f9
To: sip:test@sip.voice.google.com;tag=b6af3ec14a5583bc5e0a35f4053f902f.d201
Call-ID: 1650476025@192.168.99.53
CSeq: 1 OPTIONS
Server: OpenSIPS (1.6.2-notls (x86_64/linux))
Content-Length: 0

Ey, ¡si nos ha contestado un OpenSIPS! Esto pinta bien, vamos a probar un INVITE:

INVITE sip:+14049397606@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:39101;rport;branch=z9hG4bKPjuwdK-wRVFy7dsJD4xYtZSpD4vIKEcKRC
Max-Forwards: 70
From: "Saúl" ;tag=9rNjdJVXH-y-5I8i1pzaTQ2lJnH83ktq
To:
Contact:
Call-ID: xvpzBMnseFltW2KzzbBAO4DX-ILHionW
CSeq: 15643 INVITE
Allow: SUBSCRIBE, NOTIFY, PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, MESSAGE, REFER
Supported: 100rel, norefersub
User-Agent: sipsimple 0.17.1
Content-Type: application/sdp
Content-Length:   281

v=0
o=- 3508522483 3508522483 IN IP4 192.168.99.53
s=sipsimple 0.17.1
c=IN IP4 192.168.99.53
t=0 0
m=audio 50026 RTP/AVP 9 8 0 101
a=rtcp:50027
a=rtpmap:9 G722/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv

--

2011-03-07 22:34:43.943855: RECEIVED: Packet 2, +0:00:00.150306
74.125.95.192:5060 -(SIP over UDP)-> 192.168.99.53:39101
SIP/2.0 100 Giving a try
Via: SIP/2.0/UDP 192.168.99.53:39101;rport=61928;branch=z9hG4bKPjuwdK-wRVFy7dsJD4xYtZSpD4vIKEcKRC;received=62.131.6.55
From: "Saúl" ;tag=9rNjdJVXH-y-5I8i1pzaTQ2lJnH83ktq
To:
Call-ID: xvpzBMnseFltW2KzzbBAO4DX-ILHionW
CSeq: 15643 INVITE
Server: OpenSIPS (1.6.2-notls (x86_64/linux))
Content-Length: 0

--

2011-03-07 22:34:44.004037: RECEIVED: Packet 3, +0:00:00.210488
74.125.95.192:5060 -(SIP over UDP)-> 192.168.99.53:39101
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.99.53:39101;received=62.131.6.55;rport=61928;branch=z9hG4bKPjuwdK-wRVFy7dsJD4xYtZSpD4vIKEcKRC
Record-Route:
From: "Saúl" ;tag=9rNjdJVXH-y-5I8i1pzaTQ2lJnH83ktq
To: ;tag=1537395514
Call-ID: xvpzBMnseFltW2KzzbBAO4DX-ILHionW
CSeq: 15643 INVITE
Server: YATE/3.0.0
Contact:
Allow: ACK, INVITE, BYE, CANCEL, REGISTER, REFER, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 183

v=0
o=yate 1299533672 1299533672 IN IP4 74.125.94.83
s=SIP Call
c=IN IP4 74.125.94.83
t=0 0
m=audio 65392 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000

--
2011-03-07 22:34:44.004432: SENDING: Packet 4, +0:00:00.210883
192.168.99.53:39101 -(SIP over UDP)-> 74.125.95.192:5060
ACK sip:+14049397606@10.13.154.2:7654 SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:39101;rport;branch=z9hG4bKPj8Cj2D-inZu80ylX.FAmnAxfo3-NkXkVv
Max-Forwards: 70
From: "Saúl" ;tag=9rNjdJVXH-y-5I8i1pzaTQ2lJnH83ktq
To: ;tag=1537395514
Call-ID: xvpzBMnseFltW2KzzbBAO4DX-ILHionW
CSeq: 15643 ACK
Route:
User-Agent: sipsimple 0.17.1
Content-Length:  0

Nos ha contestado un YATE, interesante… ¿y si el usuario no existe?

INVITE sip:test@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:39101;rport;branch=z9hG4bKPjbIhDd.7vIzCwrZggWb1vRZ1KXCGSo2xw
Max-Forwards: 70
From: "Saúl" ;tag=o-QeubGVq.yh3Q-WN9yipgfWGOaexiPT
To:
Contact:
Call-ID: .O6U-GGy0TcQuD5fpM47wsiJXRiC0IN.
CSeq: 25827 INVITE
Allow: SUBSCRIBE, NOTIFY, PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, MESSAGE, REFER
Supported: 100rel, norefersub
User-Agent: sipsimple 0.17.1
Content-Type: application/sdp
Content-Length:   281

v=0
o=- 3508523286 3508523286 IN IP4 192.168.99.53
s=sipsimple 0.17.1
c=IN IP4 192.168.99.53
t=0 0
m=audio 50028 RTP/AVP 9 8 0 101
a=rtcp:50029
a=rtpmap:9 G722/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv

--

2011-03-07 22:48:06.431428: RECEIVED: Packet 8, +0:13:22.637879
74.125.95.192:5060 -(SIP over UDP)-> 192.168.99.53:39101
SIP/2.0 404 Not Found
Via: SIP/2.0/UDP 192.168.99.53:39101;rport=64041;branch=z9hG4bKPjbIhDd.7vIzCwrZggWb1vRZ1KXCGSo2xw;received=62.131.6.55
From: "Saúl" ;tag=o-QeubGVq.yh3Q-WN9yipgfWGOaexiPT
To: ;tag=b6af3ec14a5583bc5e0a35f4053f902f.2904
Call-ID: .O6U-GGy0TcQuD5fpM47wsiJXRiC0IN.
CSeq: 25827 INVITE
Server: OpenSIPS (1.6.2-notls (x86_64/linux))
Content-Length: 0

--

2011-03-07 22:48:06.431510: SENDING: Packet 9, +0:13:22.637961
192.168.99.53:39101 -(SIP over UDP)-> 74.125.95.192:5060
ACK sip:test@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:39101;rport;branch=z9hG4bKPjbIhDd.7vIzCwrZggWb1vRZ1KXCGSo2xw
Max-Forwards: 70
From: "Saúl" ;tag=o-QeubGVq.yh3Q-WN9yipgfWGOaexiPT
To: ;tag=b6af3ec14a5583bc5e0a35f4053f902f.2904
Call-ID: .O6U-GGy0TcQuD5fpM47wsiJXRiC0IN.
CSeq: 25827 ACK
User-Agent: sipsimple 0.17.1
Content-Length:  0

Vuelve a contestarnos OpenSIPS. Por lo que parece OpenSIPS balancea la carga de YATE.

Voy a tentar la suerte y mandar un MESSAGE:

2011-03-07 22:51:44.024234: SENDING: Packet 13, +0:00:06.016478
192.168.99.53:46854 -(SIP over UDP)-> 74.125.95.192:5060
MESSAGE sip:+14049397606@sip.voice.google.com SIP/2.0
Via: SIP/2.0/UDP 192.168.99.53:46854;rport;branch=z9hG4bKPjp8qvrhlbAINZHmHSl1P9s9QlDZqtKsVG
Max-Forwards: 70
From: "Saúl" ;tag=3SMMO5vwdhW4LOXNXFcfy4dOfiAkOvzo
To:
Call-ID: vcMiSLdsP56mxb5rIY6os94QuCWJ12V-
CSeq: 23424 MESSAGE
User-Agent: sipsimple 0.17.1
Content-Type: text/plain
Content-Length:     4

test
--

Could not deliver MESSAGE: 404 Not Found
2011-03-07 22:51:44.170734: RECEIVED: Packet 14, +0:00:06.162978
74.125.95.192:5060 -(SIP over UDP)-> 192.168.99.53:46854
SIP/2.0 404 Not Found
Via: SIP/2.0/UDP 192.168.99.53:46854;rport=64109;branch=z9hG4bKPjp8qvrhlbAINZHmHSl1P9s9QlDZqtKsVG;received=62.131.6.55
From: "Saúl" ;tag=3SMMO5vwdhW4LOXNXFcfy4dOfiAkOvzo
To: ;tag=b6af3ec14a5583bc5e0a35f4053f902f.7ee2
Call-ID: vcMiSLdsP56mxb5rIY6os94QuCWJ12V-
CSeq: 23424 MESSAGE
Server: OpenSIPS (1.6.2-notls (x86_64/linux))
Content-Length: 0

Una pena, esto hubiera sido para nota. Aunque lo extraño es que no rechaza el MESSAGE, dice que el usuario no existe.

Veremos con qué nos sorprende Google, espero que sea algo decente por lo que vender lo poco de alma que me pueda quedar ;-)

NOTA: Si intentáis probar esto hoy ya no funcionará, no se si ayer funcionaba por un descuido o no, pero hoy lo han deshabilitado :-(