协议扩展-用例

协议扩展可用于以下用例。

  • 基于消息的负载平衡 (MBLB)
  • 流媒体
  • 基于令牌的负载均衡
  • 负载平衡持久性
  • 基于 TCP 连接的负载平衡
  • 基于内容的负载均衡
  • SSL
  • 修改流量
  • 将流量发送到客户端或服务器
  • 连接建立的处理数据

基于消息的负载平衡

协议扩展支持基于消息的负载平衡 (MBLB),它可以解析 Citrix ADC 设备上的任何协议,并对到达一个客户端连接的协议消息进行负载平衡,即通过多个服务器连接分发消息。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 用于将消息发送到其他处理模块。除了目标上下文之外,发送 API 还将事件名称和可选负载作为参数。事件名称和行为的回调函数名称之间存在一对一的对应关系。事件的回调在 上调用 <event_name>。回调名称仅使用小写。

例如,TCP 客户端和服务器 on_data 回调是名为“DATA”的事件的用户定义处理程序。对于在一次发送调用中发送整个协议消息,使用 EOM 事件。EOM 代表消息结束,表示协议消息到 LB 上下文流的结束,因此对于此消息后面的数据,将做出新的负载平衡决策。

扩展代码有时可能不会在 on_data 事件中收到整个协议消息。在这种情况下,数据可以通过使用 ctxt: hold () API 来保存。保留 API 可用于 TCP 客户端和服务器回调上下文。当调用“保持数据”时,数据存储在上下文中。当在同一上下文中接收到更多数据时,新接收的数据会附加到先前存储的数据,并且使用组合的数据再次调用 on_data 回调函数。

注意: 使用的负载平衡方法取决于对应于负载平衡上下文的负载平衡虚拟服务器的配置。

以下代码段显示了使用发送 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 设备上的内存使用量,并会使设备容易受到 DDoS 攻击,因为许多连接上具有不完整的协议消息的 Citrix ADC 设备上的内存。

用户可以通过使用发送 API 在扩展回调处理程序中实现 TCP 数据流。数据可以分块发送,而不是在收集整个消息之前保存数据。通过使用 DATA 事件将数据发送到 ctxt.output 会发送部分协议消息。它可以跟随更多的数据事件。必须发送 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 用于发送持久性参数。要使用负载平衡持久性,您必须在默认负载平衡虚拟服务器上设置 USERSISE 持久性类型,并通过使用 user_session 字段调用发送 API 来从扩展代码中提供持久性参数。持久化参数值的最大长度为 64 字节。

如果自定义协议需要多种类型的持久性,则必须定义用户持久性类型并对其进行配置。用于配置虚拟服务器的参数名称由协议实现者决定。参数的配置值也可用于扩展代码。

以下 CLI 和代码段显示了使用发送 API 来支持负载平衡持久性。部分中的代码列表mqtt.lua 的代码列表还说明了 user_session 字段的使用。

对于持久性,您必须在负载平衡虚拟服务器上指定 USERSISE 持久性类型,并从 ns.send API 传递 user_session 值。

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

将 MQTT 消息发送到负载平衡器,并在负载中将 user_session 字段设置为 ClientiID。

示例:

-- 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 事件的发送 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 的使用。代码将客户端 ID 映射到使用 Lua 表 lb_map 对虚拟服务器名称 (lbname) 进行负载平衡,然后使用 lb_connect () 获取 lbname 的 LB 上下文。最后使用发送 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 卸载

服务器连接复用

有时,客户端一次发送一个请求,只有在从服务器收到第一个请求的响应后才发送下一个请求。在这种情况下,服务器连接可以重复用于其他客户端连接,以及在同一连接上的下一条消息,在响应发送到客户端之后。要允许其他客户端连接重复使用服务器连接,您必须在服务器端上下文中使用 ctxt: reuse_server_连接() API。

注意:此 API 在 Citrix ADC 12.1 版本 49.xx 及更高版本中可用。

修改流量

要修改请求或响应中的数据,必须使用使用高级策略 PI 表达式的本机重写功能。由于您不能在扩展中使用 PI 表达式,因此您可以使用以下 API 修改 TCP 流数据。

数据:替换(偏移量,长度,new_string)
数据:插入(偏移量,新字符串)
数据:删除(偏移量,长度)
数据:gsub(模式,替换 [,n])

以下代码段显示了 replace () API 的使用。

— 获取模式的偏移量,我们想要替换
局部旧模式 =“重新宫殿的模式”
局部旧模式 _ 长度 = 旧模式:len ()
local pat_off, pat_end = data:find(old_pattern)
-- pattern is not present
if (not pat_off) then
goto send_data
end
— 如果我们要修改的数据不完全存在,那么
— 等待更多数据
if (not pat_end) then
ctxt:hold(data)
data = nil
goto done
end
数据:替换(模式关闭,旧模式长度,“新模式”)
::send_data::
ns.send(ctxt.output,“EOM”, {data = data})
::done::

以下代码段显示了插入 () API 的使用。

数据:插入(5,“要插入的模式”)

下面的代码片段显示了插入()API 的使用,当我们想在某个模式之后或之前插入:

— 获取模式的偏移量,在它之后或之前我们要插入
局部模式 =“我们需要插入之后/之前的模式”
局部模式 _ 长度 = 模式:len ()
本地关闭,pat_end = 数据:查找(模式)
-- pattern is not present
if (not pat_off) then
goto send_data
end
— 如果我们要插入的模式不是
— 完全存在,然后等待更多数据
if (not pat_end) then
ctxt:hold(data)
data = nil
goto done
end
— 在模式之后插入
数据:插入(pat_end + 1,“要插入的模式”)
— 在模式之前插入
数据:插入(pat_off,“要插入的模式”)
::send_data::
ns.send(ctxt.output,“EOM”, {data = data})
::done::

以下代码段显示了删除 () API 的使用。

— 获取模式的偏移量,我们要删除
本地删除模式 =“要删除的模式”
本地删除模式 _ 长度 = 删除模式:len ()
local pat_off, pat_end = data:find(old_pattern)
-- pattern is not present
if (not pat_off) then
goto send_data
end
— 如果我们要删除的数据不完全存在,
-然后等待更多数据
if (not pat_end) then
ctxt:hold(data)
data = nil
goto done
end
数据:删除(模式关闭,删除模式长度)
::send_data::
ns.send(ctxt.output,“EOM”, {data = data})
::done::

以下代码段显示了 gsub () API 的使用。

— 用新字符串替换模式的所有实例
数据:gsub(“旧模式”,“新字符串”)
— 仅替换 2 个“旧模式”的实例
数据:gsub(“旧模式”,“新字符串”,2)
— 在“http”的所有实例之前插入 new_string
数据:gsub (“输入数据”,“(http)”,“新建字符串% 1”)
— 在“http”的所有实例之后插入 new_string
数据:gsub(“输入数据”,“(http)”,“%1new_string”)
— 仅在 2 个“http”实例之前插入 new_string
数据:gsub (“输入数据”,“(http)”,“新建字符串% 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: close () API 的使用。

— 如果输入数据包不是 MQTT 连接类型,则
― 向客户端发送一些错误响应。
function client.on_data(ctxt, payload)
local data = payload.data
局部偏移量 = 1
local msg_type = 0
本地错误响应 =“缺少 MQTT 连接数据包”。
字节 = 数据:字节(偏移量)
msg_type = bit32.rshift(byte, 4)
if (msg_type ~= 1) then
— 发送错误响应
ns.send(ctxt.客户端,“数据”,{数据 = 错误 _ 响应})
— 由于错误响应已发送,所以现在关闭连接
ctx: 关闭 ()
结束

以下代码段显示了用户可以在正常流量流中注入数据的示例。

— 发送请求后,向服务器发送一些日志消息。
function client.on_data(ctxt, payload)
local data = payload.data
本地日志消息 =“客户端 ID:“.. 数据:sub (3, 7)..”用户名:“数据:sub (9, 15)
— 将我们从客户端获得的请求发送到后端服务器
ns.send(ctxt.输出,“数据”,{数据 = 数据})
发送请求后,还发送日志消息
ns.send(ctxt.输出,“数据”,{数据 = 日志消息”})
结束

以下代码段显示了 ctxt.to_server API 的使用。

— 如果 HTTP 响应状态消息为“未找到”,
― 然后向服务器发送另一个请求。
函数服务器 .on_data(ctxt,有效负载)
local data = payload.data
local request“GET /default.html HTTP/1.1\r\n\r\n”ss
本地开始,结束 = 数据:查找(“未找到”)
如果(开始)然后
— 向服务器发送另一个请求
ns.send(ctxt.server,“数据”,{数据 = 请求})
结束

注意:此 API 在 Citrix ADC 12.1 版本 50.xx 及更高版本中可用。

连接建立的数据处理

可能存在一个用例,您希望在连接建立时发送一些数据(当接收最终 ACK 时)。例如,在代理协议中,您可能希望在建立连接时将客户端的源和目标 IP 地址和端口发送到后端服务器。在这种情况下,您可以使用 client.init () 回调处理程序发送连接建立时的数据。

下面的代码片段显示了对 client.init () 回调的使用:

— 向下一个处理上下文发送请求
― 在连接建立上。
函数客户端 .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.输出,“数据”,{数据 = 请求})
end

注意:此 API 在 Citrix ADC 13.0 版本 xx.xx 及更高版本中可用。