Extensions de protocole - cas d’utilisation

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

  • Équilibrage de charge basé sur les messages (MBLB)
  • Diffusion en continu
  • É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
  • Trafic d’origine vers le client ou le serveur
  • Traitement des données sur l’établissement de 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 peut analyser n’importe quel protocole sur une appliance Citrix ADC et équilibrer la charge les messages de protocole arrivant sur une connexion client, c’est-à-dire distribuer les messages sur plusieurs connexions serveur. MBLB est réalisé par le code utilisateur qui analyse le flux de données TCP client.

Le flux de données TCP est transmis aux callbacks on_data pour les comportements client et serveur. Le flux de données TCP est disponible pour les 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 comprennent :

data:len()

data:find()

data:byte()

data:sub()

data:split()

Une fois que le flux de données TCP a été analysé dans un message de protocole, le code utilisateur obtient l’équilibrage de charge en envoyant simplement le message de protocole au contexte suivant disponible à partir du contexte passé au rappel on_data pour le client.

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

Par exemple, le client TCP et le serveur on_data callbacks sont des gestionnaires définis par l’utilisateur pour les événements nommés « DATA ». Pour envoyer le message de protocole entier dans un appel d’envoi, l’événement EOM (fin du message) est utilisé. EOM (fin du message), qui signifie la fin du message, signifie la fin du message de protocole vers le flux en aval du contexte LB, de sorte qu’une nouvelle décision d’équilibrage de charge est 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 dans 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 pour les contextes client TCP-et serveur-callback. Lorsque « hold with data » est appelé, 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 send 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

Diffusion en continu

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

Les utilisateurs peuvent obtenir le streaming des données TCP dans les gestionnaires de rappel d’extension à l’aide de l’API send. Au lieu de conserver les données jusqu’à ce que l’ensemble du message soit collecté, les données peuvent être envoyées en morceaux. L’envoi de données à ctxt.output à l’aide de l’événement DATA envoie un message de protocole partiel. Il peut être suivi par d’autres événements DATA. Un événement EOM (fin du message) 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 les premières données reçues. Une nouvelle décision d’équilibrage de charge est prise après la réception du message EOM (fin du message).

Pour diffuser des données de message de protocole, envoyez plusieurs événements DATA suivis d’un événement EOM (fin du message). Les événements DATA contigus et l’événement EOM (fin du message) 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.

Pour un contexte d’envoi au client, les événements EOM (fin du message) et DATA sont effectivement les mêmes, car il n’y a pas de traitement spécial par le contexte client en aval pour les événements EOM (fin du message).

Équilibrage de charge basé sur des jetons

Pour les protocoles pris en charge en mode natif, une appliance Citrix ADC prend en charge une méthode d’équilibrage de charge basée sur un jeton qui utilise des expressions PI pour créer le jeton. Pour les extensions, le protocole n’est pas connu à l’avance, de sorte que les expressions PI ne peuvent pas être utilisées. Pour l’équilibrage de charge basé sur un jeton, vous devez définir le serveur virtuel d’équilibrage de charge par défaut pour utiliser la méthode d’équilibrage de charge USER_TOKEN et fournir la valeur de jeton à partir du code d’extension en appelant l’API send avec un champ user_token. Si la valeur du jeton est envoyée à partir de l’API send 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 send pour envoyer une valeur de 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})

Persistance de l’équilibrage de charge

La persistance de l’équilibrage de charge est étroitement liée à l’équilibrage de charge basé sur un jeton. Les utilisateurs doivent pouvoir calculer par programmation la valeur de session de persistance et l’utiliser pour la persistance d’équilibrage de charge. L’API send 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 send 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 des 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 des 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, avec le champ user_session défini 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})

Équilibrage de charge basé sur la connexion TCP

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

L’équilibrage de charge basé sur la connexion TCP peut être réalisé en utilisant l’API send avec uniquement des événements DATA et en n’envoyant aucune EOM (fin du message). Ainsi, le contexte d’équilibrage de charge en aval basera 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 après que la décision d’équilibrage de charge a été prise. Le contournement des appels d’extension se traduit par de meilleures performances, car le trafic est traité uniquement par du code natif. Le contournement peut être fait en utilisant 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 dans le contexte de sortie. Effectivement, le module à partir duquel l’appel pipe() est effectué est retiré 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 code dans la section Liste de codes pour mqtt.lua illustre également comment faire le streaming et l’utilisation de pipe() API 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)

Équilibrage de charge basé sur le contenu

Pour les protocoles natifs, la fonctionnalité de commutation de contenu comme pour les extensions de protocole est prise en charge. Avec 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 fonction de commutation de contenu pour les extensions de protocole est obtenue à l’aide de l’API ctxt:lb_connect (<lbname>). Cette API est disponible pour le contexte client TCP. En utilisant 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 send 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 de type protocole utilisateur
  • L’état du serveur virtuel n’est pas UP
  • Serveur virtuel est un serveur virtuel utilisateur, pas d’équilibrage de charge serveur virtuel

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 pour équilibrer la charge des noms de serveurs virtuels (lbname) à l’aide de la table Lua lb_map, puis obtient le contexte LB pour lbname à l’aide de lb_connect (). Et envoie enfin au contexte LB en utilisant l’API send.

    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 pour les protocoles utilisant des extensions est pris en charge de manière similaire à la façon dont SSL pour les protocoles natifs est pris en charge. En utilisant le même code d’analyse pour créer des protocoles personnalisés, vous pouvez créer une instance de protocole sur TCP ou SSL qui peut ensuite être utilisée pour configurer les serveurs virtuels. De même, vous pouvez ajouter des services utilisateur sur TCP ou SSL.

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

Multiplexage de connexion au serveur

Parfois, le client envoie une requête à la fois et envoie la requête suivante seulement après la réception de la réponse de la première demande du serveur. 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 permettre la réutilisation de la connexion serveur par d’autres connexions client, vous devez utiliser l’API ctxt : reuse_server_connection() sur le contexte côté serveur.

Remarque : Cette API est disponible dans Citrix ADC 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 fonction de réécriture native qui utilise une expression PI de stratégie avancée. Comme 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”
old_pattern_length locale = 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 modèle :

-- 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 ().

— Récupère le décalage du motif, nous voulons supprimer
   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().

    — Remplace toutes les instances du modèle par la nouvelle chaîne
data:gsub(“old pattern”, “new string”)
— Remplace seulement 2 instances de « old pattern »
data:gsub(“old pattern”, “new string”, 2)
— Insérer new_string avant toutes les instances de « http »
data:gsub(“input data”, “(http)”, “new_string%1”)
— Insérer new_string après toutes les instances de « http »
data:gsub(“input data”, “(http)”, “%1new_string”)
— Insérer new_string avant seulement 2 instances de « http »
data:gsub(“input data”, “(http)”, “new_string%1”, 2)

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

Trafic d’origine 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 client, vous devez utiliser ctxt.client comme cible. Pour envoyer ou recevoir une réponse directement avec un serveur principal à partir du contexte 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 () à partir du contexte client ou serveur. Cette API ferme la connexion côté client ou toutes les connexions serveur qui lui sont liées.

Lorsque vous appelez l’API ctxt:close (), le code d’extension envoie le paquet TCP FIN aux connexions client et serveur et si d’autres données 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 où 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 Citrix ADC 12.1 build 50.xx et versions ultérieures.

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

Il peut y avoir un cas d’utilisation où vous souhaitez envoyer des données à l’établissement de connexion (lorsque l’ACK final est reçu). Par exemple, dans le protocole proxy, vous pouvez 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 sur 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 Citrix ADC 13.0 build xx.xx et versions ultérieures.