Protokollerweiterungen - Anwendungsfälle

Protokollerweiterungen können für die folgenden Anwendungsfälle verwendet werden.

  • Nachrichtenbasierter Lastausgleich (MBLB)
  • Streaming
  • Token basierter Lastausgleich
  • Persistenz des Lastausgleichs
  • TCP-Verbindung basierter Lastausgleich
  • Inhaltsbasierter Lastausgleich
  • SSL
  • Datenverkehr ändern
  • Datenverkehr zum Client oder Server ableiten
  • Prozessdaten zum Verbindungsaufbau

Nachrichtenbasierter Lastausgleich

Protokollerweiterungen unterstützen Message Based Load Balancing (MBLB), mit dem jedes Protokoll auf einer Citrix ADC Appliance analysiert und die Protokollmeldungen, die auf einer Clientverbindung eingehen, Lastverteilung erfolgt, d. h. die Nachrichten über mehrere Serververbindungen. MBLB wird durch Benutzercode erreicht, der den Client-TCP-Datenstrom analysiert.

Der TCP-Datenstrom wird an die on_data Callbacks für Client- und Serververhalten übergeben. Der TCP-Datenstrom steht den Erweiterungsfunktionen über eine Lua-String-ähnliche Schnittstelle zur Verfügung. Sie können eine API ähnlich der Lua-String-API verwenden, um den TCP-Datenstrom zu analysieren.

Zu den nützlichen APIs gehören:

data:len()

data:find()

data:byte()

data:sub()

data:split()

Sobald der TCP-Datenstrom in eine Protokollnachricht analysiert wurde, erreicht der Benutzercode den Lastausgleich, indem er die Protokollnachricht einfach an den nächsten Kontext sendet, der aus dem Kontext verfügbar ist, der an den on_data Callback für den Client übergeben wird.

Die ns.send () API wird verwendet, um Nachrichten an andere Verarbeitungsmodule zu senden. Zusätzlich zum Zielkontext verwendet die send-API den Ereignisnamen und die optionale Nutzlast als Argumente. Es gibt Eins-zu-Eins-Korrespondenz zwischen dem Ereignisnamen und den Callback-Funktionsnamen für die Verhaltensweisen. Die Callbacks für Ereignisse werden mit on_<event_name> aufgerufen. Die Callback-Namen verwenden nur Kleinbuchstaben.

Beispielsweise sind der TCP-Client und der Server on_data Callbacks benutzerdefinierte Handler für Ereignisse mit dem Namen DATA. Für das Senden der gesamten Protokollnachricht in einem Sendeaufruf wird das EOM-Ereignis verwendet. EOM, die für Ende der Nachricht steht, bedeutet das Ende der Protokollnachricht an den LB-Kontext Downstream, so dass eine neue Lastausgleichsentscheidung für Daten getroffen wird, die dieser Nachricht folgen.

Der Erweiterungscode erhält manchmal nicht die gesamte Protokollnachricht im on_data Ereignis. In einem solchen Fall können die Daten mithilfe der ctxt:hold () API gespeichert werden. Die Hold-API ist sowohl für TCP-Client- als auch für Server-Callback-Kontexte verfügbar. Wenn hold with data aufgerufen wird, werden die Daten im Kontext gespeichert. Wenn mehr Daten im selben Kontext empfangen werden, werden die neu empfangenen Daten an die zuvor gespeicherten Daten angehängt und die on_data Callback-Funktion mit den kombinierten Daten erneut aufgerufen.

Hinweis: Die verwendete Load Balancing-Methode hängt von der Konfiguration des virtuellen Lastausgleichsservers ab, der dem Lastenausgleichskontext entspricht.

Der folgende Codeausschnitt zeigt die Verwendung der Sende-API zum Senden der analysierten Protokollnachricht.

Beispiel:

    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

In einigen Szenarien ist das Halten des TCP-Datenstroms möglicherweise nicht erforderlich, bis die gesamte Protokollnachricht erfasst wird. In der Tat wird es nicht empfohlen, es sei denn, es ist erforderlich. Das Halten der Daten erhöht die Speicherauslastung auf der Citrix ADC Appliance und kann die Appliance für DDoS-Angriffe anfällig machen, indem der Speicher auf der Citrix ADC-Appliance mit unvollständigen Protokollmeldungen an vielen Verbindungen belegt wird.

Benutzer können das Streaming von TCP-Daten in den Extension-Callback-Handlern mithilfe der Send-API erreichen. Anstatt die Daten zu halten, bis die gesamte Nachricht erfasst ist, können Daten in Blöcken gesendet werden. Das Senden von Daten an ctxt.output mithilfe des DATA -Ereignisses sendet eine partielle Protokollmeldung. Es kann durch weitere DATA Ereignisse gefolgt werden. Ein EOM-Ereignis muss gesendet werden, um das Ende der Protokollnachricht zu markieren. Der Lastausgleichskontext nachgeschaltete Lastenausgleich entscheidet über die ersten empfangenen Daten. Nach Erhalt der EOM-Nachricht wird eine neue Lastausgleichsentscheidung getroffen.

Um Protokollnachrichtendaten zu streamen, senden Sie mehrere DATA Ereignisse gefolgt von einem EOM-Ereignis. Die zusammenhängenden DATA-Ereignisse und das folgende EOM-Ereignis werden an dieselbe Serververbindung gesendet, die durch Lastausgleichsentscheidung für das erste DATA-Ereignis in der Sequenz ausgewählt wurde.

Für einen Send to Client-Kontext sind EOM und DATA Ereignisse effektiv identisch, da es keine spezielle Behandlung durch den Clientkontext nachgeschaltet für EOM-Ereignisse gibt.

Token basierter Lastausgleich

Bei nativ unterstützten Protokollen unterstützt eine Citrix ADC Appliance eine tokenbasierte Load Balancing-Methode, die PI-Ausdrücke zum Erstellen des Token verwendet. Bei Erweiterungen ist das Protokoll nicht im Voraus bekannt, so dass PI-Ausdrücke nicht verwendet werden können. Für den token basierten Lastenausgleich müssen Sie den standardmäßigen virtuellen Lastausgleichsserver so festlegen, dass die USER_TOKEN Lastausgleichsmethode verwendet wird, und den Tokenwert aus dem Erweiterungscode angeben, indem Sie die Sende-API mit einem user_token Feld aufrufen. Wenn der Tokenwert von der Sende-API gesendet wird und die USER_TOKEN Lastausgleichsmethode auf dem virtuellen Standardserver für Lastausgleich konfiguriert ist, wird die Entscheidung für den Lastausgleich getroffen, indem ein Hash basierend auf dem Tokenwert berechnet wird. Die maximale Länge des Tokenwerts beträgt 64 Byte.

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

Der Codeausschnitt im folgenden Beispiel verwendet eine Sende-API, um einen LB-Tokenwert zu senden.

Beispiel:

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

Persistenz des Lastausgleichs

Die Persistenz des Lastausgleichs hängt eng mit dem token basierten Lastausgleich zusammen. Benutzer müssen in der Lage sein, den Wert der Persistenzsitzung programmgesteuert zu berechnen und ihn für die Persistenz des Lastenausgleichs zu verwenden. Die Sende-API wird verwendet, um Persistenzparameter zu senden. Um die Persistenz des Lastenausgleichs zu verwenden, müssen Sie den Persistenztyp USERSESSION auf dem virtuellen Standardserver für Lastenausgleich festlegen und einen Persistenzparameter aus dem Erweiterungscode bereitstellen, indem Sie die Sende-API mit einem user_session Feld aufrufen. Die maximale Länge des Persistenz-Parameterwerts beträgt 64 Byte.

Wenn Sie mehrere Persistenzarten für ein benutzerdefiniertes Protokoll benötigen, müssen Sie Benutzerpersistenztypen definieren und konfigurieren. Die Namen der Parameter, die zum Konfigurieren der virtuellen Server verwendet werden, werden vom Protokollimplementierer festgelegt. Der konfigurierte Wert eines Parameters ist auch für den Erweiterungscode verfügbar.

Die folgende CLI und Codeausschnitt zeigen die Verwendung einer Sende-API zur Unterstützung der Persistenz des Lastenausgleichs. Die Codeauflistung im Abschnitt Codeauflistung für mqtt.lua veranschaulicht auch die Verwendung des Feldes user_session.

Für die Persistenz müssen Sie den Persistenztyp USERSESSION auf dem virtuellen Lastausgleichsserver angeben und den Wert user_session von der ns.send API übergeben.

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

Senden Sie die MQTT-Nachricht an den Load Balancer, wobei das Feld user_session in der Payload auf ClientID festgelegt ist.

Beispiel:

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

TCP-Verbindung basierter Lastausgleich

Für einige Protokolle wird MBLB möglicherweise nicht benötigt. Stattdessen benötigen Sie möglicherweise auf TCP-Verbindung basierenden Lastenausgleich. Beispielsweise muss das MQTT-Protokoll den anfänglichen Teil des TCP-Streams analysieren, um das Token für den Lastenausgleich zu bestimmen. Und alle MQTT-Nachrichten auf derselben TCP-Verbindung müssen an dieselbe Serververbindung gesendet werden.

TCP-Verbindung basierter Lastausgleich kann erreicht werden, indem die Send-API nur mit DATA Ereignissen verwendet wird und keine EOM gesendet wird. Auf diese Weise basiert der nachgeschaltete Lastausgleichskontext die Lastenausgleichsentscheidung auf den zuerst empfangenen Daten und sendet alle nachfolgenden Daten an dieselbe Serververbindung, die durch die Lastausgleichsentscheidung ausgewählt wurde.

Außerdem erfordern einige Anwendungsfälle möglicherweise die Möglichkeit, die Erweiterungsbehandlung zu umgehen, nachdem die Entscheidung für den Lastausgleich getroffen wurde. Die Umgehung der Erweiterungsaufrufe führt zu einer besseren Leistung, da der Datenverkehr rein durch systemeigenen Code verarbeitet wird. Umgehung kann mit der ns.pipe () API erfolgen. Ein Aufruf des pipe-API-Erweiterungscodes kann den Eingabekontext mit einem Ausgabekontext verbinden. Nach dem Aufruf von pipe () gehen alle Ereignisse, die aus dem Eingabekontext kommen, direkt in den Ausgabekontext. Effektiv wird das Modul, von dem der pipe () -Aufruf ausgeführt wird, aus der Pipeline entfernt.

Das folgende Code-Snippet zeigt Streaming und die Verwendung der pipe () API, um ein Modul zu umgehen. Die Codeauflistung im Abschnitt Codeauflistung für mqtt.lua veranschaulicht auch, wie man Streaming und die Verwendung der pipe () API verwendet, um das Modul für den Rest des Datenverkehrs auf der Verbindung zu Bypass.

Beispiel:

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

Inhaltsbasierter Lastausgleich

Bei nativen Protokollen wird eine Funktion für Protokollerweiterungen unterstützt, die Content Switching ähnelt. Mit dieser Funktion können Sie die Daten nicht an den Standardlastausgleich senden, sondern an den ausgewählten Load Balancer senden.

Das Content Switching für Protokollerweiterungen wird mit der API ctxt:lb_connect (<lbname>) erreicht. Diese API ist für den TCP-Clientkontext verfügbar. Mit dieser API kann der Erweiterungscode einen Lastausgleichskontext abrufen, der einem bereits konfigurierten virtuellen Lastausgleichsserver entspricht. Sie können dann die Send-API mit dem so erhaltenen Load Balancing-Kontext verwenden.

Der lb-Kontext kann manchmal NULL sein:

  • Virtueller Server ist nicht vorhanden
  • Virtueller Server ist nicht vom Benutzerprotokolltyp
  • Der Status des virtuellen Servers ist nicht UP
  • Virtueller Server ist ein virtueller Benutzerserver, kein Lastenausgleich virtueller Server

Wenn Sie den virtuellen Zielserver für Lastenausgleich entfernen, wenn er verwendet wird, werden alle Verbindungen zurückgesetzt, die diesem virtuellen Lastausgleichsserver zugeordnet sind.

Das folgende Code-Snippet zeigt die Verwendung der lb_connect () API. Der Code ordnet die Client-ID zum Lastenausgleich virtueller Servernamen (lbname) mithilfe der Lua-Tabelle lb_map zu und ruft dann den LB-Kontext für lbname mit lb_connect () ab. Und schließlich sendet an den LB-Kontext mit Send-API.

    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 für Protokolle, die Erweiterungen verwenden, wird ähnlich wie SSL für native Protokolle unterstützt. Mit demselben Parsing-Code zum Erstellen benutzerdefinierter Protokolle können Sie eine Protokollinstanz über TCP oder über SSL erstellen, die dann zur Konfiguration der virtuellen Server verwendet werden kann. Ebenso können Sie Benutzerdienste über TCP oder SSL hinzufügen.

Weitere Informationen finden Sie unter Konfigurieren von SSL-Offloading für MQTT und Konfigurieren von SSL-Offloading für MQTT mit End-to-End-Verschlüsselung.

Serververbindungs-Multiplexing

Manchmal sendet der Client jeweils eine Anforderung und sendet die nächste Anforderung erst, nachdem die Antwort für die erste Anforderung vom Server empfangen wurde. In einem solchen Fall kann die Serververbindung für andere Clientverbindungen und für die nächste Nachricht auf derselben Verbindung wiederverwendet werden, nachdem die Antwort an den Client gesendet wurde. Um die Wiederverwendung der Serververbindung durch andere Clientverbindungen zu ermöglichen, müssen Sie die API ctxt: reuse_server_connection () im serverseitigen Kontext verwenden.

Hinweis: Diese API ist in Citrix ADC 12.1 Build 49.xx und höher verfügbar.

Datenverkehr ändern

Um Daten in der Anforderung oder Antwort zu ändern, müssen Sie das native Rewrite-Feature verwenden, das einen erweiterten Richtlinien-PI-Ausdruck verwendet. Da Sie PI-Ausdrücke in Erweiterungen nicht verwenden können, können Sie die folgenden APIs verwenden, um TCP-Streamdaten zu ändern.

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

Das folgende Code-Snippet zeigt die Verwendung von replace () API.

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

Das folgende Code-Snippet zeigt die Verwendung von insert () API.

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

Das folgende Code-Snippet zeigt die Verwendung von insert () API, wenn wir nach oder vor einem Muster einfügen möchten:

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

Das folgende Code-Snippet zeigt die Verwendung von 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::

Das folgende Code-Snippet zeigt die Verwendung von gsub () API.

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

Hinweis: Diese API ist in Citrix ADC 12.1 Build 50.xx und höher verfügbar.

Datenverkehr zum Client oder Server ableiten

Sie können die ns.send() API verwenden, um Daten, die aus dem Erweiterungscode stammen, an einen Client und einen Back-End-Server zu senden. Um eine Antwort direkt mit einem Client zu senden oder zu empfangen, müssen Sie ctxt.client als Ziel verwenden. Um eine Antwort direkt mit einem Back-End-Server aus dem Serverkontext zu senden oder zu empfangen, müssen Sie ctxt.server als Ziel verwenden. Die Daten in der Nutzlast können ein TCP-Stream-Daten oder eine Lua-Zeichenfolge sein.

Um die Datenverkehrsverarbeitung für eine Verbindung zu stoppen, können Sie die ctxt:close() -API entweder vom Client- oder vom Serverkontext aus verwenden. Diese API schließt die clientseitige Verbindung oder alle damit verknüpften Serververbindungen.

Wenn Sie die ctxt:close() API aufrufen, sendet der Erweiterungscode TCP-FIN-Paket an die Client- und Serververbindungen. Wenn mehr Daten vom Client oder Server auf dieser Verbindung empfangen werden, setzt die Appliance die Verbindung zurück.

Das folgende Codeausschnitt zeigt die Verwendung von ctxt.client und ctxt:close () APIs.

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

Der folgende Codeausschnitt zeigt das Beispiel, wenn der Benutzer die Daten in den normalen Verkehrsfluss injizieren kann.

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

Der folgende Codeausschnitt zeigt die Verwendung der ctxt.to_server API.

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

Hinweis: Diese API ist in Citrix ADC 12.1 Build 50.xx und höher verfügbar.

Datenverarbeitung am Verbindungsaufbau

Es kann einen Anwendungsfall geben, in dem Sie einige Daten an die Verbindungseinrichtung senden möchten (wenn die endgültige ACK empfangen wird). Beispielsweise können Sie im Proxyprotokoll die Quell- und Ziel-IP-Adressen und -Ports des Clients an den Back-End-Server beim Verbindungsaufbau senden. In diesem Fall können Sie client.init () Callback-Handler verwenden, um die Daten über die Verbindungseinrichtung zu senden.

Der folgende Codeausschnitt zeigt die Verwendung von client.init() Callback:

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

Hinweis: Diese API ist in Citrix ADC 13.0 Build xx.xx und höher verfügbar.