Protokollerweiterungen - Anwendungsfälle

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

  • Nachrichtenbasiertes Load Balancing (MBLB)
  • Streamen
  • Token-basierter Lastenausgleich
  • Persistenz beim Lastenausgleich
  • Auf TCP-Verbindungen basierender Lastenausgleich
  • Inhaltsbasierter Lastenausgleich
  • SSL
  • Verkehr ändern
  • Versenden Sie den Datenverkehr zum Client oder Server
  • Prozessdaten zum Verbindungsaufbau

Nachrichtenbasierter Lastenausgleich

Protokollerweiterungen unterstützen Message Based Load Balancing (MBLB), mit dem jedes Protokoll auf einer NetScaler-Appliance analysiert und die über eine Client-Verbindung eingehenden Protokollnachrichten lastenverteilt werden können, d. h. die Nachrichten über mehrere Serververbindungen verteilt werden können. MBLB wird durch Benutzercode erreicht, der den Client-TCP-Datenstrom analysiert.

Der TCP-Datenstrom wird an die on_data-Callbacks für das Client- und Serververhalten übergeben. Der TCP-Datenstrom steht den Erweiterungsfunktionen über eine Schnittstelle wie eine Lua-Zeichenfolge zur Verfügung. Sie können eine API verwenden, die der Lua-String-API ähnelt, 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 geparst wurde, erreicht der Benutzercode den Lastenausgleich, indem er die Protokollnachricht einfach an den nächsten verfügbaren Kontext aus dem Kontext sendet, der an den on_data-Callback für den Client übergeben wurde.

Die ns.send () API wird verwendet, um Nachrichten an andere Verarbeitungsmodule zu senden. Zusätzlich zum Zielkontext verwendet die Sende-API den Eventnamen und die optionale Payload als Argumente. Es besteht eine Eins-zu-Eins-Entsprechung zwischen dem Eventnamen und den Namen der Callback-Funktionen für die Verhaltensweisen. <event_name>Die Callbacks für Ereignisse werden on_ aufgerufen. Die Callback-Namen verwenden nur Kleinbuchstaben.

Beispielsweise sind die on_data-Callbacks des TCP-Clients und des Servers benutzerdefinierte Handler für Ereignisse mit dem Namen „DATA“. Um die gesamte Protokollnachricht in einem Sendeaufruf zu senden, wird das EOM-Ereignis verwendet. EOM, das für Ende der Nachricht steht, bedeutet das Ende der Protokollnachricht an den LB-Kontext im Downstream, sodass für Daten, die auf diese Nachricht folgen, eine neue Load-Balancing-Entscheidung getroffen wird.

Der Erweiterungscode empfängt im Ereignis on_data manchmal nicht die gesamte Protokollnachricht. 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 „Mit Daten speichern“ aufgerufen wird, werden die Daten im Kontext gespeichert. Wenn mehr Daten im gleichen Kontext empfangen werden, werden die neu empfangenen Daten an die zuvor gespeicherten Daten angehängt und die on_data Callback-Funktion wird erneut mit den kombinierten Daten aufgerufen.

Hinweis: Die verwendete Lastausgleichsmethode hängt von der Konfiguration des virtuellen Lastausgleichsservers ab, die dem Lastausgleichskontext entspricht.

Der folgende Codeausschnitt zeigt die Verwendung der Sende-API zum Senden der geparsten 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-->

Streamen

In einigen Szenarien ist es möglicherweise nicht erforderlich, den TCP-Datenstrom so lange zu halten, bis die gesamte Protokollnachricht erfasst ist. Tatsächlich wird es nicht empfohlen, es sei denn, es ist erforderlich. Das Speichern der Daten erhöht die Speichernutzung auf der NetScaler-Appliance und kann die Appliance anfällig für DDoS-Angriffe machen, da der Speicher der NetScaler-Appliance mit unvollständigen Protokollmeldungen auf vielen Verbindungen erschöpft wird.

Benutzer können TCP-Daten in den Callback-Handlern der Erweiterung streamen, indem sie die Sende-API verwenden. Anstatt die Daten so lange zu speichern, bis die gesamte Nachricht erfasst ist, können Daten in Blöcken gesendet werden. Beim Senden von Daten an ctxt.output mithilfe des DATA-Ereignisses wird eine teilweise Protokollnachricht gesendet. Darauf können weitere DATA-Ereignisse folgen. Ein EOM-Ereignis muss gesendet werden, um das Ende der Protokollnachricht zu markieren. Der nachgeschaltete Load-Balancing-Kontext trifft die Lastausgleichsentscheidung für die ersten empfangenen Daten. Nach Erhalt der EOM-Nachricht wird eine neue Load-Balancing-Entscheidung getroffen.

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

Bei einem Send-to-Client-Kontext sind EOM- und DATA-Ereignisse praktisch identisch, da es für EOM-Ereignisse keine spezielle Behandlung durch den nachgeschalteten Client-Kontext gibt.

Token-basierter Lastenausgleich

Für nativ unterstützte Protokolle unterstützt eine NetScaler-Appliance eine tokenbasierte Load-Balancing-Methode, die PI-Ausdrücke verwendet, um das Token zu erstellen. Bei Erweiterungen ist das Protokoll nicht im Voraus bekannt, sodass PI-Ausdrücke nicht verwendet werden können. Für den tokenbasierten Lastenausgleich müssen Sie den standardmäßigen virtuellen Lastausgleichsserver so einrichten, dass er die USER_TOKEN-Load-Balancing-Methode verwendet, und den Token-Wert aus dem Erweiterungscode angeben, indem Sie die Sende-API mit einem Feld user_token aufrufen. Wenn der Token-Wert von der Sende-API gesendet wird und die USER_TOKEN-Ladausgleichsmethode auf dem virtuellen Standardserver für den Lastausgleich konfiguriert ist, wird die Lastausgleichsentscheidung getroffen, indem ein Hash auf der Grundlage des Tokenwerts 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 beim Lastenausgleich

Die Persistenz des Load-Balancings steht in engem Zusammenhang mit dem tokenbasierten Load Balancing. Benutzer müssen in der Lage sein, den Wert der Persistenzsitzung programmgesteuert zu berechnen und ihn für die Persistenz beim Load Balancing zu verwenden. Die Sende-API wird verwendet, um Persistenzparameter zu senden. Um die Load Balancing-Persistenz zu verwenden, müssen Sie den Persistenztyp USERSESSION auf dem virtuellen Standardserver für den Lastausgleich festlegen und einen Persistenzparameter aus dem Erweiterungscode bereitstellen, indem Sie die Sende-API mit einem Feld user_session aufrufen. Die maximale Länge des Persistenzparameterwerts beträgt 64 Byte.

Wenn Sie mehrere Persistenztypen für ein benutzerdefiniertes Protokoll benötigen, müssen Sie Benutzerpersistentypen definieren und konfigurieren. Die Namen der Parameter, die zur Konfiguration der virtuellen Server verwendet werden, werden vom Protokollimplementer 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 gesetzt 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-->

Auf TCP-Verbindungen basierender Lastenausgleich

Für einige Protokolle ist MBLB möglicherweise nicht erforderlich. Stattdessen benötigen Sie möglicherweise einen auf TCP-Verbindungen basierenden Lastenausgleich. Beispielsweise muss das MQTT-Protokoll den ersten Teil des TCP-Streams analysieren, um das Token für den Lastenausgleich zu ermitteln. Und alle MQTT-Nachrichten auf derselben TCP-Verbindung müssen an dieselbe Serververbindung gesendet werden.

Ein auf TCP-Verbindungen basierender Lastenausgleich kann erreicht werden, indem die Sende-API nur mit DATA-Ereignissen verwendet wird und kein EOM gesendet wird. Auf diese Weise stützt der Downstream-Load-Balancing-Kontext die Lastausgleichsentscheidung auf die zuerst empfangenen Daten und sendet alle nachfolgenden Daten an dieselbe Serververbindung, die durch die Lastausgleichsentscheidung ausgewählt wurde.

In einigen Anwendungsfällen ist es möglicherweise auch erforderlich, die Bearbeitung von Erweiterungen zu Bypass, nachdem die Entscheidung für den Lastenausgleich getroffen wurde. Das Umgehen der Nebenstellenaufrufe führt zu einer besseren Leistung, da der Datenverkehr ausschließlich durch systemeigenen Code verarbeitet wird. Die Umgehung kann mithilfe 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. Tatsächlich wird das Modul, von dem aus der pipe () -Aufruf erfolgt, 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 Lastenausgleich

Bei nativen Protokollen wird die Funktion zum Content Switching wie bei Protokollerweiterungen unterstützt. Mit dieser Funktion können Sie die Daten an den ausgewählten Load Balancer senden, anstatt die Daten an den Standard-Load Balancer zu senden.

Die Funktion zum Umschalten von Inhalten für Protokollerweiterungen wird mithilfe der ctxt:lb_connect () -API erreicht.<lbname> Diese API ist für den TCP-Clientkontext verfügbar. Mithilfe dieser API kann der Erweiterungscode einen Lastausgleichskontext abrufen, der einem bereits konfigurierten virtuellen Load-Balancing-Server entspricht. Sie können dann die Sende-API mit dem so erhaltenen Load-Balancing-Kontext verwenden.

Der LB-Kontext kann manchmal NULL sein:

  • Virtueller Server ist nicht vorhanden
  • Der virtuelle Server ist nicht vom Typ Benutzerprotokoll
  • Der Status des virtuellen Servers ist nicht UP
  • Der virtuelle Server ist ein virtueller Benutzerserver, kein virtueller Lastausgleichsserver

Wenn Sie den virtuellen Ziel-Lastausgleichsserver entfernen, während er verwendet wird, werden alle mit diesem virtuellen Lastausgleichsserver verknüpften Verbindungen zurückgesetzt.

Der folgende Codeausschnitt zeigt die Verwendung der lb_connect () -API. Der Code ordnet die Client-ID mithilfe der Lua-Tabelle lb_map den Namen virtueller Load-Balancing-Server (lbname) zu und ruft dann mit lb_connect () den LB-Kontext für lbname ab. Und schließlich sendet es mithilfe der Send-API an den LB-Kontext.

    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 auf ähnliche Weise unterstützt, wie SSL für native Protokolle unterstützt wird. Mit demselben Parsing-Code für die Erstellung 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 Anfrage und sendet die nächste Anfrage erst, nachdem die Antwort für die erste Anfrage vom Server empfangen wurde. In einem solchen Fall kann die Serververbindung für andere Client-Verbindungen 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 Client-Verbindungen zu ermöglichen, müssen Sie die API ctxt: reuse_server_connection () im serverseitigen Kontext verwenden.

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

Verkehr ändern

Um Daten in der Anfrage oder Antwort zu ändern, müssen Sie die systemeigene Rewrite-Funktion verwenden, die einen erweiterten Policy-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 NetScaler 12.1 Build 50.xx und höher verfügbar.

Versenden Sie den Datenverkehr zum Client oder Server

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

Um die Verarbeitung des Datenverkehrs auf einer Verbindung zu beenden, 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 ein TCP-FIN-Paket an die Client- und Serververbindungen. Wenn über diese Verbindung mehr Daten vom Client oder Server 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 NetScaler 12.1 Build 50.xx und höher verfügbar.

Datenverarbeitung beim Verbindungsaufbau

Es kann einen Anwendungsfall geben, in dem Sie einige Daten beim Verbindungsaufbau senden möchten (wenn das endgültige ACK empfangen wird). Im Proxyprotokoll möchten Sie beispielsweise die Quell- und Ziel-IP-Adressen und -Ports des Clients beim Verbindungsaufbau an den Backend-Server senden. In diesem Fall können Sie den Callback-Handler client.init () verwenden, um die Daten beim Verbindungsaufbau 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 NetScaler 13.0 Build xx.xx und höher verfügbar.