Extensions de protocole - cas d’utilisation

Les extensions de protocole peuvent être utilisées pour les cas d’utilisation suivants.

  • Équilibrage de charge basé sur les messages (MBLB)
  • Streaming
  • Équilibrage de charge basé sur des jetons
  • Persistance de l’équilibrage de charge
  • Équilibrage de charge basé sur la connexion TCP
  • Équilibrage de charge basé sur le contenu
  • SSL
  • Modifier le trafic
  • Générer du trafic vers le client ou le serveur
  • Données de traitement relatives à l’établissement de la connexion

Équilibrage de charge basé sur les messages

Les extensions de protocole prennent en charge l’équilibrage de charge basé sur les messages (MBLB), qui permet d’analyser n’importe quel protocole sur une appliance NetScaler et d’équilibrer la charge des messages de protocole arrivant sur une connexion client, c’est-à-dire de distribuer les messages sur plusieurs connexions serveur. Le MBLB est obtenu par un code utilisateur qui analyse le flux de données TCP du client.

Le flux de données TCP est transmis aux rappels on_data pour les comportements du client et du serveur. Le flux de données TCP est accessible aux fonctions d’extension via une interface de type chaîne Lua. Vous pouvez utiliser une API similaire à l’API de chaîne Lua pour analyser le flux de données TCP.

Les API utiles incluent :

data:len()

data:find()

data:byte()

data:sub()

data:split()

Une fois que le flux de données TCP a été analysé en un message de protocole, le code utilisateur équilibre la charge en envoyant simplement le message de protocole au prochain contexte disponible à partir du contexte transmis au rappel on_data pour le client.

L’API ns.send () est utilisée pour envoyer des messages à d’autres modules de traitement. Outre le contexte de destination, l’API d’envoi prend le nom de l’événement et la charge utile facultative comme arguments. Il existe une correspondance biunivoque entre le nom de l’événement et les noms des fonctions de rappel pour les comportements. <event_name>Les rappels pour les événements sont appelés on_. Les noms de rappel utilisent uniquement des minuscules.

Par exemple, les rappels on_data du client et du serveur TCP sont des gestionnaires définis par l’utilisateur pour les événements nommés « DATA ». Pour envoyer l’intégralité du message de protocole en un seul appel d’envoi, l’événement EOM est utilisé. EOM, qui signifie fin de message, signifie la fin du message de protocole envoyé au contexte LB en aval. Une nouvelle décision d’équilibrage de charge est donc prise pour les données qui suivent ce message.

Le code d’extension peut parfois ne pas recevoir l’intégralité du message de protocole lors de l’événement on_data. Dans ce cas, les données peuvent être conservées à l’aide de l’API ctxt:hold (). L’API hold est disponible à la fois pour les contextes TCP client et serveur de rappel. Lorsque l’option « Conserver les données » est appelée, les données sont stockées dans le contexte. Lorsque d’autres données sont reçues dans le même contexte, les données nouvellement reçues sont ajoutées aux données précédemment stockées et la fonction de rappel on_data est appelée à nouveau avec les données combinées.

Remarque : La méthode d’équilibrage de charge utilisée dépend de la configuration du serveur virtuel d’équilibrage de charge correspondant au contexte d’équilibrage de charge.

L’extrait de code suivant montre l’utilisation de l’API d’envoi pour envoyer le message de protocole analysé.

Exemple :

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

Streaming

Dans certains scénarios, il peut ne pas être nécessaire de suspendre le flux de données TCP jusqu’à ce que l’intégralité du message de protocole soit collectée. En fait, ce n’est pas conseillé à moins que cela ne soit nécessaire. La conservation des données augmente l’utilisation de la mémoire sur l’appliance NetScaler et peut rendre l’appliance vulnérable aux attaques DDoS en épuisant la mémoire de l’appliance NetScaler avec des messages de protocole incomplets sur de nombreuses connexions.

Les utilisateurs peuvent diffuser des données TCP dans les gestionnaires de rappel des extensions à l’aide de l’API d’envoi. Au lieu de conserver les données jusqu’à ce que l’ensemble du message soit collecté, les données peuvent être envoyées par morceaux. L’envoi de données à ctxt.output à l’aide de l’événement DATA envoie un message de protocole partiel. Elle peut être suivie d’autres événements DATA. Un événement EOM doit être envoyé pour marquer la fin du message de protocole. Le contexte d’équilibrage de charge en aval prend la décision d’équilibrage de charge sur la base des premières données reçues. Une nouvelle décision d’équilibrage de charge est prise après la réception du message EOM.

Pour diffuser les données des messages de protocole, envoyez plusieurs événements DATA suivis d’un événement EOM. Les événements DATA contigus et l’événement EOM suivant sont envoyés à la même connexion serveur sélectionnée par décision d’équilibrage de charge pour le premier événement DATA de la séquence.

Dans le cas d’un contexte d’envoi vers un client, les événements EOM et DATA sont en fait identiques, car le contexte client ne gère pas spécialement les événements EOM en aval.

Équilibrage de charge basé sur des jetons

Pour les protocoles pris en charge nativement, une appliance NetScaler prend en charge une méthode d’équilibrage de charge basée sur des jetons qui utilise des expressions PI pour créer le jeton. Pour les extensions, le protocole n’étant pas connu à l’avance, les expressions PI ne peuvent pas être utilisées. Pour l’équilibrage de charge basé sur des jetons, vous devez définir le serveur virtuel d’équilibrage de charge par défaut pour qu’il utilise la méthode d’équilibrage de charge USER_TOKEN et fournir la valeur du jeton à partir du code d’extension en appelant l’API d’envoi avec un champ user_token. Si la valeur du jeton est envoyée depuis l’API d’envoi et que la méthode d’équilibrage de charge USER_TOKEN est configurée sur le serveur virtuel d’équilibrage de charge par défaut, la décision d’équilibrage de charge est prise en calculant un hachage basé sur la valeur du jeton. La longueur maximale de la valeur du jeton est de 64 octets.

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

L’extrait de code de l’exemple suivant utilise une API d’envoi pour envoyer la valeur d’un jeton LB.

Exemple :

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

Persistance de l’équilibrage de charge

La persistance de l’équilibrage de charge est étroitement liée à l’équilibrage de charge basé sur des jetons. Les utilisateurs doivent être en mesure de calculer par programmation la valeur de la session de persistance et de l’utiliser pour équilibrer la charge de persistance. L’API d’envoi est utilisée pour envoyer des paramètres de persistance. Pour utiliser la persistance de l’équilibrage de charge, vous devez définir le type de persistance USERSESSION sur le serveur virtuel d’équilibrage de charge par défaut et fournir un paramètre de persistance à partir du code d’extension en appelant l’API d’envoi avec un champ user_session. La longueur maximale de la valeur du paramètre de persistance est de 64 octets.

Si vous avez besoin de plusieurs types de persistance pour un protocole personnalisé, vous devez définir les types de persistance utilisateur et les configurer. Les noms des paramètres utilisés pour configurer les serveurs virtuels sont déterminés par l’implémenteur du protocole. La valeur configurée d’un paramètre est également disponible pour le code d’extension.

L’interface de ligne de commande et l’extrait de code suivants montrent l’utilisation d’une API d’envoi pour prendre en charge la persistance de l’équilibrage de charge. La liste de codes dans la section Liste de codes pour mqtt.lua illustre également l’utilisation du champ user_session.

Pour la persistance, vous devez spécifier le type de persistance USERSESSION sur le serveur virtuel d’équilibrage de charge et transmettre la valeur user_session à partir de l’API ns.send.

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

Envoyez le message MQTT à l’équilibreur de charge, en définissant le champ user_session sur ClientID dans la charge utile.

Exemple :

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

Équilibrage de charge basé sur la connexion TCP

Pour certains protocoles, le MBLB peut ne pas être nécessaire. Au lieu de cela, vous aurez peut-être besoin d’un équilibrage de charge basé sur une connexion TCP. Par exemple, le protocole MQTT doit analyser la partie initiale du flux TCP pour déterminer le jeton pour l’équilibrage de charge. De plus, tous les messages MQTT de la même connexion TCP doivent être envoyés à la même connexion au serveur.

L’équilibrage de charge basé sur une connexion TCP peut être réalisé en utilisant l’API d’envoi avec uniquement des événements DATA et en n’envoyant aucun EOM. Ainsi, le contexte d’équilibrage de charge en aval base la décision d’équilibrage de charge sur les données reçues en premier et envoie toutes les données suivantes à la même connexion serveur sélectionnée par la décision d’équilibrage de charge.

En outre, certains cas d’utilisation peuvent nécessiter la possibilité de contourner la gestion des extensions une fois que la décision d’équilibrage de charge a été prise. Le contournement des appels d’extension permet d’améliorer les performances, car le trafic est traité uniquement par du code natif. Le contournement peut être effectué à l’aide de l’API ns.pipe (). Un appel au code d’extension de l’API pipe () peut connecter le contexte d’entrée à un contexte de sortie. Après l’appel à pipe (), tous les événements provenant du contexte d’entrée vont directement au contexte de sortie. En fait, le module à partir duquel l’appel pipe () est effectué est supprimé du pipeline.

L’extrait de code suivant montre la diffusion en continu et l’utilisation de l’API pipe() pour contourner un module. La liste de codes de la section Liste de codes pour mqtt.lua illustre également comment effectuer le streaming et l’utilisation de l’API pipe () pour contourner le module pour le reste du trafic sur la connexion.

Exemple :

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

Équilibrage de charge basé sur le contenu

Pour les protocoles natifs, la fonctionnalité similaire à la commutation de contenu pour les extensions de protocole est prise en charge. Grâce à cette fonctionnalité, au lieu d’envoyer les données à l’équilibrage de charge par défaut, vous pouvez envoyer les données à l’équilibreur de charge sélectionné.

La fonctionnalité de commutation de contenu pour les extensions de protocole est réalisée à l’aide de l’API ctxt:lb_connect ().<lbname> Cette API est disponible dans le contexte du client TCP. À l’aide de cette API, le code d’extension peut obtenir un contexte d’équilibrage de charge correspondant à un serveur virtuel d’équilibrage de charge déjà configuré. Vous pouvez ensuite utiliser l’API d’envoi avec le contexte d’équilibrage de charge ainsi obtenu.

Le contexte lb peut parfois être NULL :

  • Le serveur virtuel n’existe pas
  • Le serveur virtuel n’est pas du type de protocole utilisateur
  • L’état du serveur virtuel n’est pas actif
  • Le serveur virtuel est un serveur virtuel utilisateur, et non un serveur virtuel d’équilibrage de charge

Si vous supprimez le serveur virtuel d’équilibrage de charge cible lorsqu’il est utilisé, toutes les connexions associées à ce serveur virtuel d’équilibrage de charge sont réinitialisées.

L’extrait de code suivant montre l’utilisation de l’API lb_connect (). Le code mappe l’ID client aux noms de serveurs virtuels d’équilibrage de charge (lbname) à l’aide de la table Lua lb_map, puis obtient le contexte LB pour lbname à l’aide de lb_connect (). Enfin, envoie vers le contexte LB à l’aide de l’API d’envoi.

    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

Le protocole SSL pour les protocoles utilisant des extensions est pris en charge de la même manière que le protocole SSL pour les protocoles natifs. En utilisant le même code d’analyse pour créer des protocoles personnalisés, vous pouvez créer une instance de protocole via TCP ou SSL qui peut ensuite être utilisée pour configurer les serveurs virtuels. De même, vous pouvez ajouter des services utilisateur via TCP ou SSL.

Pour plus d’informations, consultez Configuration du déchargement SSL pour MQTT et Configuration du déchargement SSL pour MQTT avec chiffrement de bout en bout.

Multiplexage de connexion serveur

Parfois, le client envoie une demande à la fois et envoie la demande suivante uniquement après réception de la réponse du serveur à la première demande. Dans ce cas, la connexion au serveur peut être réutilisée pour d’autres connexions client et pour le message suivant sur la même connexion, une fois la réponse envoyée au client. Pour autoriser la réutilisation de la connexion au serveur par d’autres connexions client, vous devez utiliser l’API ctxt : reuse_server_connection () dans le contexte côté serveur.

Remarque : Cette API est disponible dans NetScaler 12.1 build 49.xx et versions ultérieures.

Modifier le trafic

Pour modifier les données de la demande ou de la réponse, vous devez utiliser la fonctionnalité de réécriture native qui utilise une expression PI de politique avancée. Étant donné que vous ne pouvez pas utiliser d’expressions PI dans les extensions, vous pouvez utiliser les API suivantes pour modifier les données d’un flux TCP.

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

L’extrait de code suivant montre l’utilisation de l’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::

L’extrait de code suivant montre l’utilisation de l’API insert().

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

L’extrait de code suivant montre l’utilisation de l’API insert (), lorsque nous voulons insérer après ou avant un motif :

-- 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::

L’extrait de code suivant montre l’utilisation de l’API delete ().

-- 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::

L’extrait de code suivant montre l’utilisation de l’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)

Remarque : Cette API est disponible dans NetScaler 12.1 build 50.xx et versions ultérieures.

Générer du trafic vers le client ou le serveur

Vous pouvez utiliser l’API ns.send () pour envoyer des données provenant du code d’extension à un client et à un serveur principal. Pour envoyer ou recevoir une réponse directement avec un client, à partir du contexte du client, vous devez utiliser ctxt.client comme cible. Pour envoyer ou recevoir une réponse directement avec un serveur principal à partir du contexte du serveur, vous devez utiliser ctxt.server comme cible. Les données de la charge utile peuvent être des données de flux TCP ou une chaîne Lua.

Pour arrêter le traitement du trafic sur une connexion, vous pouvez utiliser l’API ctxt:close () depuis le contexte client ou serveur. Cette API ferme la connexion côté client ou toute connexion serveur qui y est liée.

Lorsque vous appelez l’API ctxt:close (), le code d’extension envoie le paquet TCP FIN aux connexions client et serveur et si des données supplémentaires sont reçues du client ou du serveur sur cette connexion, l’appliance réinitialise la connexion.

L’extrait de code suivant montre l’utilisation des API ctxt.client et 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

L’extrait de code suivant montre l’exemple lorsque l’utilisateur peut injecter les données dans le flux de trafic 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

L’extrait de code suivant montre l’utilisation de l’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

Remarque : Cette API est disponible dans NetScaler 12.1 build 50.xx et versions ultérieures.

Traitement des données sur l’établissement de connexion

Il se peut que vous souhaitiez envoyer certaines données à l’établissement de la connexion (lorsque l’ACK final est reçu). Par exemple, dans le protocole proxy, vous souhaiterez peut-être envoyer les adresses IP et les ports source et destination du client au serveur principal de l’établissement de connexion. Dans ce cas, vous pouvez utiliser le gestionnaire de rappel client.init () pour envoyer les données relatives à l’établissement de la connexion.

L’extrait de code suivant montre l’utilisation du callback 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

Remarque : Cette API est disponible dans NetScaler 13.0 build xx.xx et versions ultérieures.