domingo, 10 de febrero de 2013

Problemas de DNS con cliente VPN Cisco en Windows

Si trabajáis con redes virtuales y sistemas Cisco, es probable que alguna vez os encontréis con este problema.

Resulta que cuando un usuario de Windows utiliza el Cisco VPN Client para conectar a la VPN de su empresa, este levanta una interfaz virtual para enrutar el tráfico privado por el IPsec Tunnel creado por el cliente Cisco. Hasta aquí todo correcto y previsible.

El problema viene cuando dicha interfaz virtual toma los DNS que el peer remoto le asigna, y no te dan opción a poner tú los tuyos, ya que estos son "machacados" cada vez que conectas con la VPN. Es fácil configurar cuando se tiene acceso a todos los parámetros de ambos extremos, y en especial cuando tienes la gestión del router/firewall que está actuando como terminador de túneles (típicamente un ASA, un PIX o algún otro chisme cisquero con esas capacidades). Pero en la vida real no suele ser el caso si utilizas un servicio gestionado por compañías como Movistar (cuya flexibilidad en la gestión de la VPN deja mucho que desear).


¿Qué pasa si no tienes tus DNS? Si tu equipo pertenece a un dominio Active Directory (o alguna otra tecnología dependiente de los DNS internos de la infrastructura de tu grupo) estás vendido por completo. No sólo no podrás resolver nombres de equipos internos (sin hacer la abominable chapuza de añadir nombres al archivo hosts, que me consta que aún hay quien lo hace), sino que tendrás otros problemas relacionados con la falta de resolución interna, por ejemplo algo tan básico como la autenticación o la aplicación de directivas de grupo.

Para colmo, suele producirse un efecto indeseado más: cuando en la LAN del cliente remoto existe un rango de direccionamiento IPv4 utilizado también por la sede remota a la que se pretende llegar por VPN. En este caso, aunque el software de Cisco tiene parámetros para controlar esta circunstancia, la realidad es que siempre termina habiendo problemas.

La solución


En este caso ayuda bastante el hecho de que el propio cliente Cisco te permite lanzar un ejecutable arbitrario una vez producida la autenticación remota. Haremos un script que se encargue de lidiar con los problemas anteriormente citados.

Como primer requisito es que el usuario que lance el cliente VPN Cisco debe tener permisos locales suficientes para gestionar parámetros de las interfaces de red. Aunque he visto más de un sysadmin cazurro agregar al usuario al grupo de Administradores solamente para que disponga de estos privilegios (¡qué barbaridad!), la manera correcta y elegante de hacer esto es agregar al usuario al grupo Operadores de configuración de red. Es un grupo local y por tanto lo encontraremos en la ventana de Administración de equipos.


El siguiente paso es abrir el Cisco VPN Client para indicarle la línea de comandos del script que se ejecutará tras la conexión. Digamos que llamaremos al script vpn.vbs y que para no complicarnos mucho la vida, lo copiaremos a la propia ruta del ejecutable de Cisco. El script lo vamos a hacer en VBscript, aunque sería realmente fácil portarlo a JScript. En cualquier caso, lo ejecutaremos con CSCRIPT para poder mostrar mensajes en pantalla sin que nos aparezcan continuos "pop-ups".

Así la cosa, la ruta será la siguiente:

C:\WINDOWS\System32\cscript.exe "C:\Archivos de programa\Cisco Systems\VPN Client\VPN.vbs"
Que tendremos que especificar en la opción del menú correspondiente:



(Nota: es recomendable calcular primero las partes variables de la línea anterior usando variables de entorno para garantizar la compatibilidad: %WINDIR% para el directorio real del sistema, y %PROGRAMFILES% para el subdirectorio de archivos de programa).


Una vez preconfigurado todo, pasamos al meollo del asunto.

El script

Nuestro script hará las siguientes tareas de manera automatizada:

  • Esperar a que la interfaz virtual esté levantada
  • Obtener la dirección IP de dicha interfaz
  • Agregar una ruta dinámica para llegar a la sede remota aún cuando el rango coincida con el local
  • Borrar los DNS asignados por el extremo remoto del túnel y añadir los de nuestra compañía
  • Abandonar el script


Un ejemplo del código de dicho script es el siguiente (adaptadlo a los parámetros de vuestro escenario).

'----------------------------------------------------------------------------
'  Configurador de red para clientes CISCO VPN
'  The Debug Machine
'  http://www.debugmachine.com
'
'   Copyright 2013 José Ángel Morente

'   Licensed under the Apache License, Version 2.0 (the "License");
'   you may not use this file except in compliance with the License.
'   You may obtain a copy of the License at

'     http://www.apache.org/licenses/LICENSE-2.0

'   Unless required by applicable law or agreed to in writing, software
'   distributed under the License is distributed on an "AS IS" BASIS,
'   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
'   See the License for the specific language governing permissions and
'   limitations under the License.

'  FUNCIONAMIENTO:
'
'  Las conexiones VPN CISCO, tal y como están configuradas por Telefónica, dan
'  problemas de rutas a la red 192.168.1.0 cuando coincide con la red local, 
'  así como problemas de resolución DNS (imposibilita resolver los objetos de 
'  sistema con los DNS propios).
'
'  Este configurador ha de ejecutarse durante el proceso de conexión (usando la
'  opción "Application Launcher" del cliente VPN CISCO. El programa espera
'  hasta encontrar levantada la interfaz "CVirtA" (nombre interno de la conexión
'  Cisco VPN). Una vez levantada, halla la dirección IP, agrega una ruta para
'  forzar el tráfico de la 192.168.1.0 a través de la interfaz virtual, y 
'  finalmente selecciona los DNS internos de la compañía para que todas las
'  resoluciones se hagan a través de ahí
'
'  El script tiene varios parámetros configurables en forma de constantes.
'
'-----------------------------------------------------------------------------

Option Explicit

'CONFIGURACIÓN DEL SCRIPT
Const NICServiceName = "CVirtA"
Const strComputer = "."  'Equipo local
Const remoteRoute = "192.168.1.0"
Const remoteMask = "255.255.255.0"
Const DNSservers = "192.168.1.11,192.168.1.12,192.168.1.13"


'--------------MAIN

Dim   strIPAddress, NICIndex

WScript.Echo "Esperando a que la interfaz " & NICServiceName & " esté lista..."

'Obtiene dirección IP y la muestra
strIPAddress = GetVPNIPAddress (strComputer, NICServiceName)
WScript.Echo "Dirección IP detectada: " & strIPAddress

'Crea la ruta
WScript.Echo "Agregando ruta de sede remota a la interfaz " & strIPAddress
AddRouteIP4 strComputer, remoteRoute, remoteMask, strIPAddress, NICIndex, 22

'Configura los DNS
WScript.Echo "Configurando los DNS de la interfaz " & strIPAddress
SetDNS NICServiceName, Split (DNSservers,",")

'Salir de la aplicación
WScript.Quit 0

'--------------DECLARACION FUNCIONES

Function GetVPNIPAddress (computerName, interfaceServiceName)
'------------------------------------------------------------
'  Queda en espera hasta que se levante la interfaz de Cisco y 
'  devuelve su IP
'
'  Entrada: 
'       computerName            Nombre del equipo ("." para el equipo local)
'       interfaceServiceName    Nombre de servicio de la interfaz
'
'
'  Salida:  
'       dirección IP en formato String
'
'------------------------------------------------------------

        dim strIPAddress
        dim objWMI,colInterfaces,objInterface

        Set objWMI = GetObject("winmgmts:" _
                            & "{impersonationLevel=impersonate}!\\" & computerName & _
                            "\root\cimv2")      'Crea conexión local a la WMI

        
        'obtiene todas las interfaces llamadas deseadas y que estén activas
        'la interface de CISCO aparece como "IPenabled=false" hasta que no tiene una IP asignada
        'por tanto hacemos un bucle hasta que dicha interface sea obtenida en la consulta, lo cuál significa
        'que ya tiene una IP (IPenabled=true)
        Do              
         Set colInterfaces = objWMI.ExecQuery _
             ("Select * From Win32_NetworkAdapterConfiguration where ServiceName=""" & interfaceServiceName & _
                """ and IPEnabled=true")        
        Loop While colInterfaces.count=0
        
        For each objInterface in colInterfaces
                        strIPAddress = Join(objInterface.IPAddress, ",")
                        GetVPNIPAddress = strIPAddress
        Next
End Function

Sub AddRouteIP4 (computerName, route, mask, gateway, InterfaceIndex, metric)
'-------------------------------------------------------------------------
'  Agrega una ruta dinámica en la tabla de rutas IPv4
'  Aunque esta función podría haber sido realizada mediante WMI, para evitar
'  problemas de privilegios de usuario lo haremos ejecutando el comando "route"
'  de Windows a través de una shell.

'  Entrada: computerName


        Dim objShell, strCMD

        Set objShell = CreateObject ("WScript.Shell")   
        strCMD = "route add " & route & " mask " & mask & " " & gateway 
        
        'Parámetros "metric" e "index" pueden ser "null"
        If metric <> Null Then strCMD = strCMD  & " metric " & metric 
        If InterfaceIndex <> Null Then strCMD = strCMD & " if " & InterfaceIndex 
        
        objShell.Exec(strCMD)
        
End Sub

Sub SetDNS (interfaceServiceName, DNSlist)
        Dim objShell, i, strCMD, strNIC
        
        'Obtiene el nombre de la interfaz
        strNIC = GetInterfaceNamebyServiceName (interfaceServiceName)

        Set objShell = CreateObject ("WScript.shell")
        
        'Borra las DNS existentes
        strCMD = "netsh interface ip delete dns name=""" & strNIC & """ all"
        WScript.Echo vbTab & "Borrando DNS actualmente asignados"
        objShell.Run strCMD, 0, True 'ventana oculta y espera a que retorne
        
        'Añade las demás DNS
        For i = 0 To UBound(DNSlist)
                Select Case i+1
                        'Comando DNS primaria
                        Case 1:  strCMD = "netsh interface ip set dns name=""" & strNIC & """" & _
                                                  " source=static addr=" & DNSlist(i) & " register=PRIMARY"
                        'Comando DNS secundarias                                                  
                        Case Else: strCMD = "netsh interface ip add dns name=""" & strNIC & """" & _ 
                                                            " addr=" & DNSlist(i) & " index=" & i+1
                End Select
                WScript.Echo vbTab & "Agregando DNS: " & DNSlist(i)
                objShell.Run strCMD, 0, True
        Next
End Sub

'--------------------------------------------
'  Obtiene el nombre de la interfaz de red
'  a partir del "service name" (CVirtA)
'--------------------------------------------
Function GetInterfaceNamebyServiceName (InterfaceServiceName)
        Dim objWMI, colInterfaces, objInterface
                
        Set objWMI = GetObject("winmgmts:" _
            & "{impersonationLevel=Impersonate}!\\" & strComputer & "\root\cimv2")

        Set colInterfaces = objWMI.ExecQuery _
            ("Select * From Win32_NetworkAdapter Where ServiceName=""" & interfaceServiceName & _
                        """")
        
        If colInterfaces.count Then
                For Each objInterface in colInterfaces
                    GetInterfaceNamebyServiceName = objInterface.NetConnectionId
                Next
        End If

End Function

Espero os haya servido de ayuda.

Saludos