Extensiones de protocolo: Casos de uso

Las extensiones de protocolo se pueden usar para los siguientes casos de uso.

  • Equilibrio de carga basado en mensajes (MBLB)
  • Streaming
  • Equilibrio de carga basado en token
  • Persistencia del equilibrio de carga
  • Equilibrio de carga basado en conexión TCP
  • Equilibrio de carga basado en contenido
  • SSL
  • Modificar tráfico
  • Origen del tráfico en el cliente o servidor
  • Procesar datos sobre el establecimiento de conexión

Equilibrio de carga basado en mensajes

Las extensiones de protocolo admiten Equilibrio de carga basado en mensajes (MBLB), que puede analizar cualquier protocolo en un dispositivo Citrix ADC y equilibrar la carga los mensajes de protocolo que llegan a una conexión de cliente, es decir, distribuir los mensajes a través de varias conexiones de servidor. MBLB se logra mediante el código de usuario que analiza la secuencia de datos TCP del cliente.

La secuencia de datos TCP se pasa a las devoluciones de llamada on_data para comportamientos de cliente y servidor. El flujo de datos TCP está disponible para las funciones de extensión a través de una cadena Lua como interfaz. Puede utilizar una API similar a la API de cadena de Lua para analizar la secuencia de datos TCP.

Las API útiles incluyen:

data:len()

data:find()

data:byte()

data:sub()

data:split()

Una vez que el flujo de datos TCP se ha analizado en un mensaje de protocolo, el código de usuario logra el equilibrio de carga simplemente enviando el mensaje de protocolo al siguiente contexto disponible desde el contexto pasado a la devolución de llamada on_data para el cliente.

La API ns.send () se utiliza para enviar mensajes a otros módulos de procesamiento. Además del contexto de destino, la API de envío toma el nombre del evento y la carga útil opcional como argumentos. Hay correspondencia uno a uno entre el nombre del evento y los nombres de las funciones de devolución de llamada para los comportamientos. Las devoluciones de llamada para eventos se llaman on_ <event_name>. Los nombres de devolución de llamada utilizan solo minúsculas.

Por ejemplo, las devoluciones de llamada on_data del cliente TCP y del servidor son manejadores definidos por el usuario para eventos denominados “DATA”. Para enviar todo el mensaje de protocolo en una llamada de envío, se utiliza el evento EOM. EOM, que significa fin del mensaje, significa el final del mensaje de protocolo al flujo descendente del contexto LB, por lo que se toma una nueva decisión de equilibrio de carga para los datos que siguen a este mensaje.

Es posible que el código de extensión a veces no reciba todo el mensaje de protocolo en el evento on_data. En tal caso, los datos pueden conservarse mediante la API ctxt:hold (). La API de retención está disponible tanto para contextos TCP-cliente como de devolución de llamada de servidor. Cuando se llama a “retener con datos”, los datos se almacenan en el contexto. Cuando se reciben más datos en el mismo contexto, los datos recién recibidos se anexan a los datos almacenados anteriormente y la función de devolución de llamada on_data se vuelve a llamar con los datos combinados.

Nota: El método de equilibrio de carga utilizado depende de la configuración del servidor virtual de equilibrio de carga correspondiente al contexto de equilibrio de carga.

El siguiente fragmento de código muestra el uso de la API de envío para enviar el mensaje de protocolo analizado.

Ejemplo:

    function client.on_data(ctxt, payload)
        --
        -- code to parse payload.data into protocol message comes here
        --
        -- sending the message to lb
        ns.send(ctxt.output, "EOM", {data = message})
    end -- client.on_data

    function server.on_data(ctxt, payload)
        --
        -- code to parse payload.data into protocol message comes here
        --
        -- sending the message to client
        ns.send(ctxt.output, "EOM", {data = message})

    end -- server.on_data

Streaming

En algunos escenarios, es posible que no sea necesario mantener la secuencia de datos TCP hasta que se recopile todo el mensaje de protocolo. De hecho, no se aconseja a menos que sea necesario. Mantener los datos aumenta el uso de memoria en el dispositivo Citrix ADC y puede hacer que el dispositivo sea susceptible a ataques DDoS al agotar la memoria en el dispositivo Citrix ADC con mensajes de protocolo incompletos en muchas conexiones.

Los usuarios pueden lograr la transmisión de datos TCP en los controladores de devolución de llamada de extensión mediante la API de envío. En lugar de mantener los datos hasta que se recopile todo el mensaje, los datos se pueden enviar en fragmentos. El envío de datos a ctxt.output mediante el evento DATA envía un mensaje de protocolo parcial. Puede ser seguido por más eventos DATA. Se debe enviar un evento EOM para marcar el final del mensaje de protocolo. El contexto de equilibrio de carga descendente toma la decisión de equilibrio de carga sobre los primeros datos recibidos. Una nueva decisión de equilibrio de carga se toma después de la recepción del mensaje EOM.

Para transmitir datos de mensajes de protocolo, envíe varios eventos DATA seguidos de un evento EOM. Los eventos DATA contiguos y el siguiente evento EOM se envían a la misma conexión de servidor seleccionada por decisión de equilibrio de carga para el primer evento DATA de la secuencia.

Para un contexto de envío al cliente, los eventos EOM y DATA son efectivamente los mismos, ya que no hay un manejo especial por parte del contexto cliente downstream para eventos EOM.

Equilibrio de carga basado en token

Para los protocolos compatibles de forma nativa, un dispositivo Citrix ADC admite un método de equilibrio de carga basado en token que utiliza expresiones PI para crear el token. Para las extensiones, el protocolo no se conoce de antemano, por lo que no se pueden usar expresiones PI. Para el equilibrio de carga basado en token, debe establecer el servidor virtual de equilibrio de carga predeterminado para utilizar el método de equilibrio de carga USER_TOKEN y proporcionar el valor del token desde el código de extensión llamando a la API de envío con un campo user_token. Si el valor del token se envía desde la API de envío y el método de equilibrio de carga USER_TOKEN se configura en el servidor virtual de equilibrio de carga predeterminado, la decisión de equilibrio de carga se toma calculando un hash basado en el valor del token. La longitud máxima del valor del token es de 64 bytes.

add lb vserver v\_mqttlb USER\_TCP –lbMethod USER\_TOKEN

El fragmento de código del siguiente ejemplo utiliza una API de envío para enviar un valor de token LB.

Ejemplo:

        -- send the message to lb




        -- user_token is set to do LB based on clientID




        ns.send(ctxt.output, "EOM", {data = message,

                                 user_token = token_info})

Persistencia del equilibrio de carga

La persistencia del equilibrio de carga está estrechamente relacionada con el equilibrio de carga basado en token. Los usuarios deben poder calcular mediante programación el valor de la sesión de persistencia y usarlo para la persistencia de equilibrio de carga. La API de envío se utiliza para enviar parámetros de persistencia. Para utilizar la persistencia de equilibrio de carga, debe establecer el tipo de persistencia USERSESSION en el servidor virtual de equilibrio de carga predeterminado y proporcionar un parámetro de persistencia desde el código de extensión llamando a la API de envío con un campo user_session. La longitud máxima del valor del parámetro de persistencia es de 64 bytes.

Si necesita varios tipos de persistencia para un protocolo personalizado, debe definir tipos de persistencia de usuario y configurarlos. El implementador del protocolo decide los nombres de los parámetros utilizados para configurar los servidores virtuales. El valor configurado de un parámetro también está disponible para el código de extensión.

La siguiente CLI y fragmento de código muestra el uso de una API de envío para admitir la persistencia de equilibrio de carga. La lista de códigos de la sección Listado de códigos para mqtt.luatambién ilustra el uso del campo user_session.

Para la persistencia, debe especificar el tipo de persistencia USERSESSION en el servidor virtual de equilibrio de carga y pasar el valor user_session desde la API ns.send.

add lb vserver v\_mqttlb USER\_TCP –persistencetype USERSESSION

Envíe el mensaje MQTT al equilibrador de carga, con el campo user_session establecido en ClientID en la carga útil.

Ejemplo:

-- send the data so far to lb

-- user_session is set to clientID as well (it will be used to persist session)

ns.send(ctxt.output, “DATA”, {data = data, user_session = clientID})

Equilibrio de carga basado en conexión TCP

Para algunos protocolos, MBLB podría no ser necesario. En su lugar, es posible que necesite equilibrio de carga basado en conexión TCP. Por ejemplo, el protocolo MQTT debe analizar la parte inicial de la secuencia TCP para determinar el token para el equilibrio de carga. Además, todos los mensajes MQTT en la misma conexión TCP deben enviarse a la misma conexión de servidor.

El equilibrio de carga basado en la conexión TCP se puede lograr mediante el uso de la API de envío con solo eventos DATA y no enviando ningún EOM. De esta forma, el contexto de equilibrio de carga descendente basa la decisión de equilibrio de carga en los datos recibidos primero y envía todos los datos posteriores a la misma conexión de servidor seleccionada por la decisión de equilibrio de carga.

Además, algunos casos de uso pueden requerir la capacidad de omitir el manejo de extensiones después de que se haya tomado la decisión de balanceo de carga. Al omitir las llamadas de extensión se obtiene un mejor rendimiento, ya que el tráfico se procesa puramente mediante código nativo. Bypass se puede hacer mediante la API ns.pipe (). Una llamada al código de extensión API pipe () puede conectar el contexto de entrada a un contexto de salida. Después de la llamada a pipe (), todos los eventos provenientes del contexto de entrada van directamente al contexto de salida. Efectivamente, el módulo desde el que se realiza la llamada pipe () se elimina del proceso.

El siguiente fragmento de código muestra la transmisión y el uso de la API pipe () para omitir un módulo. La lista de códigos en la sección Listado de códigos para mqtt.luatambién ilustra cómo hacer streaming y el uso de la API pipe () para omitir el módulo para el resto del tráfico en la conexión.

Ejemplo:

        -- send the data so far to lb
        ns.send(ctxt.output, "DATA", {data = data,
                                       user_token = clientID})
        -- pipe the subsequent traffic to the lb - to bypass the client on_data handler
        ns.pipe(ctxt.input, ctxt.output)

Equilibrio de carga basado en contenido

Para los protocolos nativos, se admite el cambio de contenido como función para las extensiones de protocolo. Con esta función, en lugar de enviar los datos al balance de carga predeterminado, puede enviar los datos al equilibrador de carga seleccionado.

La función de conmutación de contenido para las extensiones de protocolo se logra mediante el uso de la API ctxt:lb_connect (<lbname>). Esta API está disponible para el contexto del cliente TCP. Mediante esta API, el código de extensión puede obtener un contexto de equilibrio de carga correspondiente a un servidor virtual de equilibrio de carga ya configurado. A continuación, puede utilizar la API de envío con el contexto de equilibrio de carga así obtenido.

El contexto lb puede ser NULL a veces:

  • El servidor virtual no existe
  • El servidor virtual no es del tipo de protocolo de usuario
  • El estado del servidor virtual no está UP
  • El servidor virtual es un servidor virtual de usuario, no un servidor virtual de equilibrio de carga

Si quita el servidor virtual de equilibrio de carga de destino cuando esté en uso, se restablecerán todas las conexiones asociadas con ese servidor virtual de equilibrio de carga.

El siguiente fragmento de código muestra el uso de la API lb_connect (). El código asigna el ID del cliente a los nombres de servidor virtual de equilibrio de carga (lbname) mediante la tabla Lua lb_map y, a continuación, obtiene el contexto LB para lbname mediante lb_connect (). Y finalmente envía al contexto LB mediante la API de envío.

    local lb_map = {
       ["client1*"] = "lb_1",
       ["client2*"] = "lb_2",
       ["client3*"] = "lb_3",
       ["client4*"] = "lb_4"
    }

    -- map the clientID to the corresponding LB vserver and connect to it
    for client_pattern, lbname in pairs(lb_map) do
       local match_idx = string.find(clientID, client_pattern)
       if (match_idx == 1) then
      lb_ctxt = ctxt:lb_connect(lbname)
      if (lb_ctxt == nil) then
         error("Failed to connect to LB vserver: " .. lbname)
      end
      break
       end
    end
    if (lb_ctxt == nil) then
    -- If lb context is NULL, the user can raise an error or send data to default LB
       error("Failed to map LB vserver for client: " .. clientID)
    end
-- send the data so far to lb
ns.send(lb_ctxt, "DATA", {data = data}

SSL

SSL para protocolos que utilizan extensiones es compatible de formas similares a la compatibilidad con SSL para protocolos nativos. Mediante el mismo código de análisis para crear protocolos personalizados, puede crear una instancia de protocolo a través de TCP o SSL que luego se puede utilizar para configurar los servidores virtuales. Del mismo modo, puede agregar servicios de usuario a través de TCP o SSL.

Para obtener más información, consulte Configuración de la descarga SSL para MQTT y Configuración de la descarga SSL para MQTT con cifrado de extremo a extremo.

Multiplexación de conexión de servidor

A veces, el cliente envía una solicitud a la vez y envía la siguiente solicitud solo después de recibir la respuesta de la primera solicitud del servidor. En tal caso, la conexión del servidor se puede volver a utilizar para otras conexiones de cliente y para el siguiente mensaje en la misma conexión, después de que la respuesta se haya enviado al cliente. Para permitir la reutilización de la conexión del servidor por otras conexiones de cliente, debe utilizar la API ctxt: Reuse_server_connection () en el contexto del servidor.

Nota: Esta API está disponible en Citrix ADC 12.1 build 49.xx y versiones posteriores.

Modificar tráfico

Para modificar datos en la solicitud o respuesta, debe utilizar la función de reescritura nativa que utiliza una expresión de PI de directiva avanzada. Dado que no puede utilizar expresiones PI en extensiones, puede utilizar las siguientes API para modificar datos de una secuencia TCP.

datos:reemplazar (desplazamiento, longitud, nuevo_cadena)
datos:insertar (offset, nuevo_cadena)
datos:eliminar (desfase, longitud)
datos:gsub (patrón, reemplazar [, n]))

El siguiente fragmento de código muestra el uso de replace () API.

— Obtener el desplazamiento del patrón, queremos reemplazar
   local_old_pattern = "patrón a sustituir"
local_pattern_length = old_pattern:len()
   local pat_off, pat_end = data:find(old_pattern)
   -- pattern is not present
if (not pat_off) then
    goto send_data
   end
  — Si los datos que queremos modificar no están completamente presentes, entonces
  — esperar más datos
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
     goto done
  end
datos:replace (pat_off, old_pattern_length, "nuevo patrón")
::send_data::
ns.send(ctxt.output, “EOM”, {data = data})
::done::

El siguiente fragmento de código muestra el uso de insert () API.

datos:insertar (5, "patrón para insertar")

El siguiente fragmento de código muestra el uso de insert () API, cuando queremos insertar después o antes de algún patrón:

— Obtener el desplazamiento del patrón, después o antes del cual queremos insertar
patrón   local = "patrón después/antes del cual tenemos que insertar"
local_longitud de patrón = patrón:len ()
pat_off   local, pat_end = datos:buscar (patrón)
-- pattern is not present
   if (not pat_off) then
    goto send_data
   end
  — Si el patrón después del cual queremos insertar no es
  — completamente presente, luego esperar más datos
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
     goto done
  end
— Insertar después del patrón
datos:insert (pat_end + 1, "patrón para insertar")
   — Insertar antes del patrón
datos:insertar (pat_off, "patrón para insertar")
::send_data::
    ns.send(ctxt.output, “EOM”, {data = data})
::done::

El siguiente fragmento de código muestra el uso de delete () API.

— Obtener el desplazamiento del patrón, queremos eliminar
   local delete_pattern = "patrón a eliminar"
local delete_pattern_length = delete_pattern:len ()
   local pat_off, pat_end = data:find(old_pattern)
   -- pattern is not present
if (not pat_off) then
          goto send_data
   end
  — Si los datos que queremos eliminar no están completamente presentes,
  — luego esperar más datos
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
    goto done
  end
datos:eliminar (pat_off, delete_pattern_length)
::send_data::
ns.send(ctxt.output, “EOM”, {data = data})
::done::

El siguiente fragmento de código muestra el uso de la API gsub ().

    — Reemplazar todas las instancias del patrón con la nueva cadena
datos:gsub ("patrón antiguo", "nueva cadena")
— Reemplazar solo 2 instancias de "patrón antiguo"
datos:gsub ("patrón antiguo", "nueva cadena", 2)
— Inserta new_string antes de todas las instancias de "http"
datos:gsub ("datos de entrada", "(http)", "nuevo_cadena%1")
— Inserta new_string después de todas las instancias de "http"
datos:gsub ("datos de entrada", "(http)", "%1nuevo_cadena")
— Inserta new_string antes de solo 2 instancias de "http"
datos:gsub ("datos de entrada", "(http)", "nuevo_cadena%1", 2)

Nota: Esta API está disponible en Citrix ADC 12.1 build 50.xx y versiones posteriores.

Origen del tráfico en el cliente o servidor

Puede utilizar la API ns.send () para enviar datos que se originan desde el código de extensión a un cliente y un servidor back-end. Para enviar o recibir respuesta directamente con un cliente, desde el contexto del cliente, debe usar ctxt.client como destino. Para enviar o recibir respuesta directamente con un servidor back-end desde el contexto del servidor, debe usar ctxt.server como destino. Los datos en la carga útil pueden ser datos de flujo TCP o una cadena Lua.

Para detener el procesamiento del tráfico en una conexión, puede usar la API ctxt:close () desde el contexto del cliente o del servidor. Esta API cierra la conexión del lado del cliente o cualquier conexión del servidor vinculada a ella.

Cuando se llama a la API ctxt:close (), el código de extensión envía el paquete TCP FIN a las conexiones cliente y servidor y, si se reciben más datos del cliente o servidor en esta conexión, el dispositivo restablece la conexión.

El siguiente fragmento de código muestra el uso de las API ctxt.client y ctxt:close ().

    — Si el paquete de entrada no es de tipo MQTT CONNECT, entonces
— enviar alguna respuesta de error al cliente.
function client.on_data(ctxt, payload)
    local data = payload.data
desplazamiento    local = 1
    local_msg_type = 0
    local error_response = "Falta el paquete MQTT Connect."
    byte = datos:byte (offset)
msg_type = bit32.rshift (byte, 4)
if (msg_type ~= 1) entonces
— Enviar la respuesta de error
   ns.send (ctxt.client, "DATA", {data = error_response})
— Dado que se ha enviado la respuesta al error, por lo que ahora cierra la conexión
    ctxt:cerrar ()
fin

El siguiente fragmento de código muestra el ejemplo cuando el usuario puede inyectar los datos en el flujo de tráfico normal.

— Después de enviar la solicitud, envíe algún mensaje de registro al servidor.
function client.on_data(ctxt, payload)
local data = payload.data
local log_message = “client id: “..data:sub(3, 7)..” user name: “ data:sub(9, 15)
— Enviar la solicitud que recibimos del cliente al servidor back-end
ns.send (ctxt.output, "DATA", {data = data})
Después de enviar la solicitud, también envíe el mensaje de registro
ns.send (ctxt.output, "DATA", {data = log_message"})
fin

El siguiente fragmento de código muestra el uso de la API ctxt.to_server.

— Si el mensaje de estado de respuesta HTTP es "Not Found",
— luego envíe otra solicitud al servidor.
función server.on_data (ctxt, carga útil)
    local data = payload.data
solicitud    local "GET /default.html http/1.1rnrn" ss
    local start, end = datos:find ("No encontrado")
    if (inicio), entonces
    — Enviar la otra solicitud al servidor
        ns.send (ctxt.server, "DATA", {data = request})
fin

Nota: Esta API está disponible en Citrix ADC 12.1 build 50.xx y versiones posteriores.

Tratamiento de datos en el establecimiento de conexión

Puede haber un caso de uso en el que quiera enviar algunos datos al establecimiento de conexión (cuando se reciba el ACK final). Por ejemplo, en el protocolo proxy, es posible que quiera enviar direcciones IP y puertos de origen y destino del cliente al servidor back-end en el establecimiento de conexión. En este caso, puede utilizar el controlador de devolución de llamada client.init () para enviar los datos en el establecimiento de la conexión.

El siguiente fragmento de código muestra el uso de la devolución de llamada client.init ():

— Enviar una solicitud al siguiente contexto de procesamiento
— en el establecimiento de conexión.
función client.init (ctxt)
solicitud   local "PROXY TCP4" + ctxt.client.ip.src.to_s + ""+ ctxt.client.ip.dst.to_s + ""+ ctxt.client.tcp.srcport + ""+     ctxt.client.tcp.dstport
— Enviar la otra solicitud al servidor
   ns.send (ctxt.output, "DATA", {data = request})
  end

Nota: Esta API está disponible en Citrix ADC 13.0 build xx.xx y versiones posteriores.