プロトコル拡張 - ユースケース

プロトコル拡張は、次のユースケースで使用できます。

  • メッセージベースの負荷分散(MBLB)
  • ストリーミング
  • トークン・ベースのロード・バランシング
  • ロード・バランシングの永続性
  • TCP 接続ベースの負荷分散
  • コンテンツ・ベースのロード・バランシング
  • SSL
  • トラフィックを変更する
  • クライアントまたはサーバへのトラフィックの発信元
  • 接続確立のデータを処理する

メッセージ・ベースのロード・バランシング

プロトコル拡張では、メッセージベース負荷分散(MBLB)がサポートされています。MBLB(メッセージベース負荷分散)は、Citrix ADCアプライアンス上の任意のプロトコルを解析し、1つのクライアント接続で受信したプロトコルメッセージを負荷分散します。MBLB は、クライアント TCP データストリームを解析するユーザーコードによって実現されます。

TCP データストリームは、クライアントとサーバーの動作のために on_data コールバックに渡されます。TCPデータストリームは、インタフェースのようなLua文字列を介して拡張関数に利用可能です。Lua 文字列 API に似た API を使用して、TCP データストリームを解析できます。

有用な API には、次のものがあります。

data:len()

data:find()

data:byte()

data:sub()

data:split()

TCPデータストリームがプロトコルメッセージに解析されると、ユーザーコードは、クライアント用のon_dataコールバックに渡されたコンテキストから利用可能な次のコンテキストにプロトコルメッセージを送信するだけで負荷分散を実現します。

ns.send () API は、他の処理モジュールにメッセージを送信するために使用されます。送信先コンテキストに加えて、send API はイベント名とオプションのペイロードを引数として受け取ります。イベント名とビヘイビアのコールバック関数名には、1 対 1 の対応があります。イベントのコールバックは on_ と呼ばれます <event_name>。コールバック名は小文字のみを使用します。

たとえば、TCP クライアントおよびサーバーの on_data コールバックは、「DATA」という名前のイベントのユーザー定義ハンドラーです。1 回の送信コールでプロトコルメッセージ全体を送信するには、EOM イベントが使用されます。EOM は、メッセージの終わりを表し、LB コンテキストのダウンストリームへのプロトコルメッセージの終わりを示します。したがって、このメッセージの後に続くデータに対して新しい負荷分散の決定が行われます。

拡張コードは、on_dataイベントでプロトコルメッセージ全体を受信しないことがあります。このような場合には、ctxt: hold () APIを使用してデータを保持することができます。保留 API は、TCP クライアントコンテキストとサーバコールバックコンテキストの両方で使用できます。「データで保持」が呼び出されると、データはコンテキストに格納されます。同じコンテキストでより多くのデータが受信されると、新しく受信したデータが以前に格納されたデータに追加され、on_dataコールバック関数が結合されたデータで再び呼び出されます。

注: 使用される負荷分散方法は、負荷分散コンテキストに対応する負荷分散仮想サーバーの構成によって異なります。

次のコードスニペットは、send API を使用して解析されたプロトコルメッセージを送信する方法を示しています。

例:

    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

ストリーミング

シナリオによっては、プロトコルメッセージ全体が収集されるまで TCP データストリームを保持する必要がない場合があります。実際には、それが必要でない限り、それは助言されません。データを保持すると、Citrix ADCアプライアンスのメモリ使用量が増加し、多くの接続で不完全なプロトコルメッセージでCitrix ADCアプライアンスのメモリを使い果たすことにより、アプライアンスがDDoS攻撃を受けやすくなります。

ユーザーは、send API を使用して、拡張コールバックハンドラで TCP データのストリーミングを実現できます。メッセージ全体が収集されるまでデータを保持する代わりに、データをチャンクで送信できます。DATA イベントを使用して ctxt.output にデータを送信すると、部分的なプロトコルメッセージが送信されます。これは、より多くのDATAイベントが続くことができます。プロトコルメッセージの終わりをマークするには、EOM イベントを送信する必要があります。負荷分散コンテキストは、最初に受信したデータに対して負荷分散の決定を行います。EOM メッセージの受信後、新しい負荷分散の決定が行われます。

プロトコルメッセージデータをストリーミングするには、複数の DATA イベントの後に EOM イベントを送信します。連続する DATA イベントと次の EOM イベントは、シーケンスの最初の DATA イベントの負荷分散の決定によって選択された同じサーバー接続に送信されます。

クライアントコンテキストに送信する場合、EOM イベントと DATA イベントは事実上同じです。これは、クライアントコンテキストのダウンストリームによる EOM イベントに対する特別な処理がないためです。

トークン・ベースのロード・バランシング

ネイティブでサポートされるプロトコルの場合、Citrix ADCアプライアンスは、PI式を使用してトークンを作成するトークンベースの負荷分散方式をサポートします。拡張の場合、プロトコルは事前に知られていないため、PI式は使用できません。トークンベースの負荷分散では、USER_TOKEN 負荷分散メソッドを使用するようにデフォルトの負荷分散仮想サーバーを設定し、user_token フィールドで送信 API を呼び出して拡張コードからトークン値を指定する必要があります。トークン値が送信 API から送信され、USER_TOKEN 負荷分散方式がデフォルトの負荷分散仮想サーバーで構成されている場合、トークンの値に基づいてハッシュを計算することによって負荷分散の決定が行われます。トークン値の最大長は 64 バイトです。

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

次の例のコードスニペットは、送信 API を使用して LB トークン値を送信します。

例:

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

ロード・バランシングの永続性

負荷分散の永続性は、トークンベースの負荷分散と密接に関連しています。ユーザーは、永続性セッション値をプログラムで計算し、永続性を負荷分散するために使用できる必要があります。送信 API は、永続性パラメータを送信するために使用されます。負荷分散の永続性を使用するには、デフォルトの負荷分散仮想サーバーで USERSESSION 永続性タイプを設定し、user_session フィールドを指定して送信 API を呼び出して、拡張コードから永続性パラメータを指定する必要があります。永続性パラメータ値の最大長は 64 バイトです。

カスタムプロトコルに複数のタイプの永続性が必要な場合は、ユーザー永続性タイプを定義して設定する必要があります。仮想サーバの設定に使用されるパラメータの名前は、プロトコル実装者によって決定されます。パラメータの設定値は、拡張コードでも使用できます。

次の CLI とコードスニペットは、負荷分散の永続性をサポートする送信 API の使用方法を示しています。mqtt.luaのコードリストセクションのコード一覧では、user_session フィールドの使用方法も示しています。

永続性については、負荷分散仮想サーバーで USERSESSION 永続性タイプを指定し、ns.send API から user_session 値を渡す必要があります。

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

ペイロードで user_session フィールドを clientID に設定して、MQTT メッセージをロードバランサーに送信します。

例:

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

TCP 接続ベースの負荷分散

一部のプロトコルでは、MBLB は必要ない場合があります。代わりに、TCP 接続ベースの負荷分散が必要になる場合があります。たとえば、MQTT プロトコルは TCP ストリームの最初の部分を解析して、負荷分散用のトークンを決定する必要があります。また、同じ TCP 接続上のすべての MQTT メッセージは、同じサーバー接続に送信する必要があります。

TCP 接続ベースの負荷分散は、DATA イベントのみで send API を使用し、EOM を送信しないことで実現できます。このようにして、ダウンストリームの負荷分散コンテキストは、最初に受信したデータに基づいて負荷分散を決定し、その後のすべてのデータを負荷分散の決定によって選択された同じサーバー接続に送信します。

また、ユースケースによっては、負荷分散の決定後に拡張処理をバイパスする機能が必要になる場合があります。拡張呼び出しをバイパスすると、トラフィックが純粋にネイティブコードで処理されるため、パフォーマンスが向上します。バイパスは、ns.pipe()APIを使用して行うことができます。pipe () API 拡張コードを呼び出すと、入力コンテキストを出力コンテキストに接続できます。pipe()の呼び出しの後、入力コンテキストからのすべてのイベントが直接出力コンテキストに移動します。実際には、pipe()呼び出しが行われたモジュールはパイプラインから削除されます。

次のコードスニペットは、ストリーミングと pipe () API を使用してモジュールをバイパスする方法を示しています。mqtt.luaのコードリストセクションのコードリストには、ストリーミングを行う方法と、接続上の残りのトラフィックについてモジュールをバイパスするための pipe () API の使用方法も示されています。

例:

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

コンテンツ・ベースのロード・バランシング

ネイティブプロトコルの場合、プロトコル拡張のコンテンツスイッチングと同様の機能がサポートされます。この機能を使用すると、デフォルトのロードバランサーにデータを送信する代わりに、選択したロードバランサーにデータを送信できます。

プロトコル拡張のコンテンツスイッチング機能は、ctxt: lb_connect (<lbname>) API を使用することによって実現されます。この API は、TCP クライアントコンテキストで使用できます。拡張コードは、この API を使用して、すでに設定されている負荷分散仮想サーバーに対応する負荷分散コンテキストを取得できます。その後、取得した負荷分散コンテキストで送信 API を使用できます。

lb コンテキストは NULL になることがあります。

  • 仮想サーバが存在しません
  • 仮想サーバーがユーザープロトコルタイプではありません
  • 仮想サーバの状態が UP ではありません
  • 仮想サーバは、負荷分散仮想サーバではなく、ユーザーの仮想サーバです。

ターゲットの負荷分散仮想サーバーの使用中に削除すると、その負荷分散仮想サーバーに関連付けられているすべての接続がリセットされます。

次のコードスニペットは、lb_connect () API の使用方法を示しています。このコードは、Lua テーブル lb_map を使用してクライアント ID を負荷分散仮想サーバー名 (lbname) にマップし、lb_connect () を使用して lbname の LB コンテキストを取得します。最後に、send APIを使用してLBコンテキストに送信します。

    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 は、ネイティブプロトコルの SSL がサポートされる方法と同様の方法でサポートされます。カスタムプロトコルを作成するために同じ解析コードを使用して、TCPまたはSSLを介してプロトコルインスタンスを作成し、仮想サーバーを構成するために使用することができます。同様に、TCP または SSL 経由でユーザーサービスを追加できます。

詳しくは、「MQTT の SSL オフロードの設定」および「エンドツーエンド暗号化を使用した MQTT の SSL オフロードの設定」を参照してください。

サーバー接続の多重化

場合によっては、クライアントは一度に 1 つの要求を送信し、最初の要求の応答がサーバーから受信された後に次の要求を送信します。この場合、サーバー接続は、応答がクライアントに送信された後、他のクライアント接続および同じ接続上の次のメッセージに再利用できます。他のクライアント接続によるサーバー接続の再利用を許可するには、サーバー側のコンテキストで ctxt: reuse_server_connection () API を使用する必要があります。

:このAPIは、Citrix ADC 12.1ビルド49.xx以降で使用できます。

トラフィックを変更する

要求または応答のデータを変更するには、高度なポリシー PI 式を使用するネイティブの書き換え機能を使用する必要があります。拡張で PI 式を使用できないため、次の API を使用して TCP ストリームデータを変更できます。

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

次のコードスニペットは、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)
— パターンが存在しません
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::

次のコードスニペットは、insert () API の使用方法を示しています。

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

次のコードスニペットは、いくつかのパターンの前または後に挿入したいときに、insert()APIの使用を示しています。

-- 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)
— パターンが存在しません
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::

次のコードスニペットは、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)
— パターンが存在しません
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::

次のコードスニペットは、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)

:このAPIは、Citrix ADC 12.1ビルド50.xx以降で使用できます。

クライアントまたはサーバへのトラフィックの発信元

ns.send () API を使用して、拡張コードから生成されたデータをクライアントとバックエンドサーバーに送信できます。クライアントコンテキストからクライアントと直接応答を送受信するには、ctxt.clientをターゲットとして使用する必要があります。サーバーコンテキストからバックエンドサーバーと直接応答を送受信するには、ctxt.serverをターゲットとして使用する必要があります。ペイロードのデータは、TCP ストリームデータまたは Lua 文字列です。

接続でのトラフィック処理を停止するには、クライアントまたはサーバコンテキストから ctxt: close () API を使用できます。この API は、クライアント側の接続またはそれにリンクされているサーバー接続を閉じます。

ctxt: close () APIを呼び出すと、拡張コードがクライアントとサーバー接続にTCP FINパケットを送信します。この接続でクライアントまたはサーバーからより多くのデータが受信されると、アプライアンスは接続をリセットします。

次のコードスニペットは、ctxt.clientとctxtの使用方法を示しています。クローズ()API。

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

次のコードスニペットは、ユーザーが通常のトラフィックフローにデータを注入できる例を示しています。

-- After sending request, send some log message to the server.
function client.on_data(ctxt, 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

次のコードスニペットは、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
— 別のリクエストをサーバに送信する
ns.send(ctxt.server, “DATA”, {data = request})
end

:このAPIは、Citrix ADC 12.1ビルド50.xx以降で使用できます。

接続確立でのデータ処理

接続確立時にデータを送信したいユースケースがあるかもしれません(最終的なACKを受信したとき)。たとえば、プロキシプロトコルでは、接続確立時にクライアントの送信元と宛先 IP アドレスとポートをバックエンドサーバーに送信できます。この場合、client.init()コールバックハンドラを使用して、接続確立時にデータを送信することができます。

次のコードスニペットは、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
— 別のリクエストをサーバに送信する
ns.send(ctxt.output, “DATA”, {data = request})
end

:このAPIは、Citrix ADC 13.0ビルドxx.xx以降で使用できます。