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)
  • Transmisión
  • 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
<!--NeedCopy-->

Transmisión

En algunos casos, 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})
<!--NeedCopy-->

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 Lista de códigos para mqtt.lua tambié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})
<!--NeedCopy-->

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 equilibrio 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 de la sección Lista de códigos para mqtt.lua también ilustra cómo hacer streaming y el uso de la API pipe () para omitir el módulo durante el resto del tráfico de 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)
<!--NeedCopy-->

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}
<!--NeedCopy-->

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, compilación 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.

data:replace(offset, length, new_string)
data:insert(offset, new_string)
data:delete(offset, length)
data:gsub(pattern, replace [,n]))

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

-- Get the offset of the pattern, we want to replace
   local old_pattern = “pattern to repalace”
local old_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
  -- If the data we want to modify is not completely present, then
  -- wait for more data
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
     goto done
  end
data:replace(pat_off, old_pattern_length, “new pattern”)
::send_data::
ns.send(ctxt.output, “EOM”, {data = data})
::done::

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

data:insert(5, “pattern to insert”)

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

-- Get the offset of the pattern, after or before which we want to insert
   local pattern = “pattern after/before which we need to insert”
local pattern_length = pattern:len()
   local pat_off, pat_end = data:find(pattern)
-- pattern is not present
   if (not pat_off) then
    goto send_data
   end
  -- If the pattern after which we want to insert is not
  -- completely present, then wait for more data
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
     goto done
  end
-- Insert after the pattern
data:insert(pat_end + 1, “pattern to insert”)
   -- Insert before the pattern
data:insert(pat_off, “pattern to insert”)
::send_data::
    ns.send(ctxt.output, “EOM”, {data = data})
::done::

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

-- Get the offset of the pattern, we want to delete
   local delete_pattern = “pattern to delete”
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
  -- If the data we want to delete is not completely present,
  -- then wait for more data
  if (not pat_end) then
        ctxt:hold(data)
        data = nil
    goto done
  end
data:delete(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 ().

    -- Replace all the instances of the pattern with the new string
data:gsub(“old pattern”, “new string”)
-- Replace only 2 instances of “old pattern”
data:gsub(“old pattern”, “new string”, 2)
-- Insert new_string before all instances of “http”
data:gsub(“input data”, “(http)”, “new_string%1”)
-- Insert new_string after all instances of “http”
data:gsub(“input data”, “(http)”, “%1new_string”)
-- Insert new_string before only 2 instances of “http”
data:gsub(“input data”, “(http)”, “new_string%1”, 2)

Nota: Esta API está disponible en Citrix ADC 12.1, compilación 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 ().

    -- If the input packet is not MQTT CONNECT type, then
-- send some error response to the client.
function client.on_data(ctxt, payload)
    local data = payload.data
    local offset = 1
    local msg_type = 0
    local error_response = “Missing MQTT Connect packet.”
    byte = data:byte(offset)
msg_type = bit32.rshift(byte, 4)
if (msg_type ~= 1) then
-- Send the error response
   ns.send(ctxt.client, “DATA”, {data = error_response})
-- Since error response has been sent, so now close the connection
    ctxt:close()
end

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

-- After sending request, send some log message to the server.
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)
-- Send the request we get from the client to backend server
ns.send(ctxt.output, “DATA”, {data = data})
After sending the request, also send the log message
ns.send(ctxt.output, “DATA”, {data = log_message”})
end

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

-- If the HTTP response status message is “Not Found”,
-- then send another request to the server.
function server.on_data(ctxt, payload)
    local data = payload.data
    local request “GET /default.html HTTP/1.1\r\n\r\n”ss
    local start, end = data:find(“Not Found”)
    if (start) then
    -- Send the another request to server
        ns.send(ctxt.server, “DATA”, {data = request})
end

Nota: Esta API está disponible en Citrix ADC 12.1, compilación 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 ():

-- Send a request to the next processing context
-- on the connection establishment.
function client.init(ctxt)
   local request “PROXY TCP4” + ctxt.client.ip.src.to_s + “ “ + ctxt.client.ip.dst.to_s + “ “ + ctxt.client.tcp.srcport + “ “ +     ctxt.client.tcp.dstport
-- Send the another request to server
   ns.send(ctxt.output, “DATA”, {data = request})
  end

Nota: Esta API está disponible en Citrix ADC 13.0, compilación xx.xx y versiones posteriores.