Extensiones de protocolo: casos de uso

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

  • Equilibrio de carga basado en mensajes (MBLB)
  • Transmisión
  • Equilibrio de carga basado en tokens
  • Persistencia del equilibrio de carga
  • Equilibrio de carga basado en conexiones TCP
  • Equilibrio de carga basado en contenido
  • SSL
  • Modificar el tráfico
  • Orientar el tráfico hacia el cliente o el servidor
  • Procesar datos sobre el establecimiento de la conexión

Equilibrio de carga basado en mensajes

Las extensiones de protocolo admiten el equilibrio de carga basado en mensajes (MBLB), que puede analizar cualquier protocolo de un dispositivo NetScaler y equilibrar la carga de 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. El MBLB se logra mediante un código de usuario que analiza el flujo de datos TCP del cliente.

El flujo de datos TCP se pasa a las llamadas de on_data para determinar el comportamiento del cliente y el servidor. El flujo de datos TCP está disponible para las funciones de extensión a través de una interfaz similar a una cadena Lua. Puede utilizar una API similar a la API de cadenas Lua para analizar el flujo 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 del cliente.

La API ns.send () se usa para enviar mensajes a otros módulos de procesamiento. Además del contexto de destino, la API de envío utiliza el nombre del evento y la carga opcional como argumentos. Existe una correspondencia individual entre el nombre del evento y los nombres de las funciones de devolución de llamada para los comportamientos. <event_name>Las llamadas devueltas para eventos se llaman en_. Los nombres de devolución de llamada solo usan minúsculas.

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

A veces, es posible que el código de extensión no reciba todo el mensaje de protocolo del evento on_data. En tal caso, los datos se pueden almacenar mediante la API ctxt:hold (). La API de retención está disponible para los contextos de cliente TCP y de devolución de llamadas de servidor. Cuando se llama “retener datos”, los datos se almacenan en el contexto. Cuando se reciben más datos en el mismo contexto, los datos recién recibidos se añaden a los datos almacenados anteriormente y se vuelve a llamar a la función de devolución de llamada on_data 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, puede que no sea necesario mantener el flujo de datos TCP hasta que se recopile todo el mensaje del protocolo. De hecho, no se recomienda a menos que sea obligatorio. Mantener los datos aumenta el uso de memoria en el dispositivo NetScaler y puede hacer que el dispositivo sea susceptible a los ataques de DDoS al agotar la memoria del dispositivo NetScaler 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 llamadas de la extensión mediante la API de envío. En lugar de guardar los datos hasta que se haya recopilado todo el mensaje, los datos se pueden enviar en fragmentos. Al enviar datos a ctxt.output mediante el evento DATA, se envía un mensaje de protocolo parcial. Puede ir seguido de más eventos de DATA. Se debe enviar un evento de EOM para marcar el final del mensaje de protocolo. El contexto de equilibrio de carga posterior toma la decisión de equilibrio de carga sobre los primeros datos recibidos. Tras recibir el mensaje de EOM, se toma una nueva decisión de equilibrio de carga.

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 de EOM se envían a la misma conexión de servidor seleccionada mediante una 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 prácticamente los mismos, ya que el contexto del cliente no gestiona de forma especial los eventos de EOM en sentido posterior.

Equilibrio de carga basado en tokens

Para los protocolos compatibles de forma nativa, un dispositivo NetScaler admite un método de equilibrio de carga basado en un token que utiliza expresiones PI para crear el token. En el caso de las extensiones, el protocolo no se conoce de antemano, por lo que no se pueden utilizar expresiones PI. Para el equilibrio de carga basado en tokens, debe configurar el servidor virtual de equilibrio de carga predeterminado para que utilice el método de equilibrio de carga USER_TOKEN y proporcionar el valor del token a partir del 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 está configurado 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 usa 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 tokens. Los usuarios deben poder calcular mediante programación el valor de la sesión de persistencia y usarlo para equilibrar la persistencia de carga. La API de envío se usa para enviar parámetros de persistencia. Para utilizar la persistencia del equilibrio de carga, debe configurar el tipo de persistencia USERSESSION en el servidor virtual de equilibrio de carga predeterminado y proporcionar un parámetro de persistencia a partir del 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 los 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 en 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 balanceador de carga, con el campo user_session establecido en ClientID en la carga.

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 conexiones TCP

Para algunos protocolos, es posible que el MBLB no sea necesario. En su lugar, es posible que necesite un equilibrio de carga basado en una conexión TCP. Por ejemplo, el protocolo MQTT debe analizar la parte inicial del flujo TCP para determinar el token para el equilibrio de carga. Además, todos los mensajes MQTT de 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 utilizando la API de envío solo con eventos DATA y sin enviar ningún EOM. De esta forma, el contexto de equilibrio de carga posterior 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 posibilidad de omitir la gestión de extensiones una vez que se haya tomado la decisión de equilibrar la carga. Si se omiten las llamadas a la extensión, se obtiene un mejor rendimiento, ya que el tráfico se procesa únicamente mediante código nativo. La omisión se puede realizar mediante la API ns.pipe (). Una llamada al código de extensión de la API pipe () puede conectar el contexto de entrada con un contexto de salida. Después de la llamada a pipe (), todos los eventos que provienen del contexto de entrada van directamente al contexto de salida. Efectivamente, el módulo desde el que se realiza la llamada pipe () se elimina de la canalización.

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 la función de cambio de contenido 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 balanceador de carga seleccionado.

La función de cambio de contenido para las extensiones de protocolo se logra mediante la API ctxt:lb_connect ().<lbname> Esta API está disponible en el contexto del cliente TCP. Con 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 NULO 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á activo
  • El servidor virtual es un servidor virtual de usuario, no un servidor virtual de equilibrio de carga

Si elimina el servidor virtual de equilibrio de carga de destino cuando está en uso, se restablecerán todas las conexiones asociadas a 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 servidores virtuales de equilibrio de carga (lbname) mediante la tabla de Lua lb_map y, a continuación, obtiene el contexto de LB de 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

El SSL para los protocolos que utilizan extensiones se admite de forma similar a como se admite el SSL para los protocolos nativos. Con 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 usar 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 solo después de recibir del servidor la respuesta de la primera solicitud. En tal caso, la conexión al servidor se puede reutilizar para otras conexiones de cliente y para el siguiente mensaje de la misma conexión, una vez que se haya enviado la respuesta al cliente. Para permitir la reutilización de la conexión del servidor por parte de otras conexiones de cliente, debe utilizar la API ctxt: reuse_server_connection () en el contexto del servidor.

Nota: Esta API está disponible en la versión 49.xx y versiones posteriores de NetScaler 12.1.

Modificar el tráfico

Para modificar los datos de la solicitud o la respuesta, debe utilizar la función de reescritura nativa que utiliza una expresión 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 NetScaler 12.1, compilación 50.xx y versiones posteriores.

Orientar el tráfico hacia el cliente o el servidor

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

Para detener el procesamiento del tráfico en una conexión, puedes 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 de servidor vinculada a ella.

Al llamar 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 NetScaler 12.1, compilación 50.xx y versiones posteriores.

Procesamiento de datos en el establecimiento de conexión

Puede haber un caso práctico en el que desee enviar algunos datos al establecimiento de conexión (cuando se reciba el ACK final). Por ejemplo, en el protocolo proxy, es posible que desee enviar las direcciones IP y los puertos de origen y destino del cliente al servidor de fondo del establecimiento de la conexión. En este caso, puede utilizar el controlador de devolución de llamadas client.init () para enviar los datos al establecer 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 NetScaler 13.0, compilación xx.xx y versiones posteriores.