Skip to content

Commit 8335234

Browse files
authored
Add support for proxy protocol info for lua plugin (#12651)
* Add support for proxy protocol info for lua plugin * remove trailing spaces * fix format error * fix format error * Fix tests
1 parent fb691b5 commit 8335234

File tree

5 files changed

+321
-0
lines changed

5 files changed

+321
-0
lines changed

doc/admin-guide/plugins/lua.en.rst

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,75 @@ Here is an example:
13401340
end
13411341

13421342

1343+
:ref:`TOP <admin-plugins-ts-lua>`
1344+
1345+
ts.client_request.get_pp_info
1346+
-----------------------------------------------
1347+
**syntax:** *value = ts.client_request.get_pp_info(key)*
1348+
1349+
**context:** do_remap/do_os_response or do_global_* or later
1350+
1351+
**description**: This function can be used to get PROXY protocol information as a string value.
1352+
1353+
The *key* parameter should be one of the following constants:
1354+
1355+
* **TS_LUA_PP_INFO_SRC_ADDR** - Source IP address
1356+
* **TS_LUA_PP_INFO_DST_ADDR** - Destination IP address
1357+
1358+
You can also pass a TLV type ID (0x00-0xFF) to retrieve custom TLV values from PROXY protocol v2.
1359+
1360+
Returns the string value if available, or **nil** if the information is not available or PROXY protocol is not in use.
1361+
1362+
Here is an example:
1363+
1364+
::
1365+
1366+
function do_remap()
1367+
local pp_version = ts.client_request.get_pp_info_int(TS_LUA_PP_INFO_VERSION)
1368+
if pp_version then
1369+
local src_addr = ts.client_request.get_pp_info(TS_LUA_PP_INFO_SRC_ADDR)
1370+
local dst_addr = ts.client_request.get_pp_info(TS_LUA_PP_INFO_DST_ADDR)
1371+
ts.debug(string.format('PROXY v%d: %s -> %s', pp_version, src_addr, dst_addr))
1372+
end
1373+
return 0
1374+
end
1375+
1376+
1377+
:ref:`TOP <admin-plugins-ts-lua>`
1378+
1379+
ts.client_request.get_pp_info_int
1380+
-----------------------------------------------
1381+
**syntax:** *value = ts.client_request.get_pp_info_int(key)*
1382+
1383+
**context:** do_remap/do_os_response or do_global_* or later
1384+
1385+
**description**: This function can be used to get PROXY protocol information as an integer value.
1386+
1387+
The *key* parameter should be one of the following constants:
1388+
1389+
* **TS_LUA_PP_INFO_VERSION** - PROXY protocol version (1 or 2)
1390+
* **TS_LUA_PP_INFO_SRC_PORT** - Source port number
1391+
* **TS_LUA_PP_INFO_DST_PORT** - Destination port number
1392+
* **TS_LUA_PP_INFO_PROTOCOL** - IP protocol family (AF_INET or AF_INET6)
1393+
* **TS_LUA_PP_INFO_SOCK_TYPE** - Socket type (SOCK_STREAM, SOCK_DGRAM, etc.)
1394+
1395+
Returns the integer value if available, or **nil** if the information is not available or PROXY protocol is not in use.
1396+
1397+
Here is an example:
1398+
1399+
::
1400+
1401+
function do_remap()
1402+
local pp_version = ts.client_request.get_pp_info_int(TS_LUA_PP_INFO_VERSION)
1403+
if pp_version then
1404+
local src_port = ts.client_request.get_pp_info_int(TS_LUA_PP_INFO_SRC_PORT)
1405+
local dst_port = ts.client_request.get_pp_info_int(TS_LUA_PP_INFO_DST_PORT)
1406+
ts.debug(string.format('PROXY v%d ports: %d -> %d', pp_version, src_port, dst_port))
1407+
end
1408+
return 0
1409+
end
1410+
1411+
13431412
:ref:`TOP <admin-plugins-ts-lua>`
13441413

13451414
ts.http.set_cache_url

plugins/lua/ts_lua_client_request.cc

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@
2020
#include <arpa/inet.h>
2121
#include "ts_lua_util.h"
2222

23+
typedef enum {
24+
TS_LUA_PP_INFO_VERSION = TS_PP_INFO_VERSION,
25+
TS_LUA_PP_INFO_SRC_ADDR = TS_PP_INFO_SRC_ADDR,
26+
TS_LUA_PP_INFO_SRC_PORT = TS_PP_INFO_SRC_PORT,
27+
TS_LUA_PP_INFO_DST_ADDR = TS_PP_INFO_DST_ADDR,
28+
TS_LUA_PP_INFO_DST_PORT = TS_PP_INFO_DST_PORT,
29+
TS_LUA_PP_INFO_PROTOCOL = TS_PP_INFO_PROTOCOL,
30+
TS_LUA_PP_INFO_SOCK_TYPE = TS_PP_INFO_SOCK_TYPE
31+
} TSLuaPPInfoKey;
32+
33+
ts_lua_var_item ts_lua_pp_info_key_vars[] = {
34+
TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_VERSION), TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_SRC_ADDR),
35+
TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_SRC_PORT), TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_DST_ADDR),
36+
TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_DST_PORT), TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_PROTOCOL),
37+
TS_LUA_MAKE_VAR_ITEM(TS_LUA_PP_INFO_SOCK_TYPE)};
38+
2339
static void ts_lua_inject_client_request_client_addr_api(lua_State *L);
2440
static void ts_lua_inject_client_request_server_addr_api(lua_State *L);
2541

@@ -78,6 +94,10 @@ static int ts_lua_client_request_get_ssl_protocol(lua_State *L);
7894
static void ts_lua_inject_client_request_ssl_curve_api(lua_State *L);
7995
static int ts_lua_client_request_get_ssl_curve(lua_State *L);
8096

97+
static void ts_lua_inject_client_request_pp_info_api(lua_State *L);
98+
static int ts_lua_client_request_get_pp_info(lua_State *L);
99+
static int ts_lua_client_request_get_pp_info_int(lua_State *L);
100+
81101
void
82102
ts_lua_inject_client_request_api(lua_State *L)
83103
{
@@ -98,6 +118,7 @@ ts_lua_inject_client_request_api(lua_State *L)
98118
ts_lua_inject_client_request_ssl_cipher_api(L);
99119
ts_lua_inject_client_request_ssl_protocol_api(L);
100120
ts_lua_inject_client_request_ssl_curve_api(L);
121+
ts_lua_inject_client_request_pp_info_api(L);
101122

102123
lua_setfield(L, -2, "client_request");
103124
}
@@ -1148,6 +1169,95 @@ ts_lua_client_request_get_ssl_curve(lua_State *L)
11481169
return 1;
11491170
}
11501171

1172+
static void
1173+
ts_lua_inject_client_request_pp_info_api(lua_State *L)
1174+
{
1175+
size_t i;
1176+
1177+
for (i = 0; i < sizeof(ts_lua_pp_info_key_vars) / sizeof(ts_lua_var_item); i++) {
1178+
lua_pushinteger(L, ts_lua_pp_info_key_vars[i].nvar);
1179+
lua_setglobal(L, ts_lua_pp_info_key_vars[i].svar);
1180+
}
1181+
1182+
lua_pushcfunction(L, ts_lua_client_request_get_pp_info);
1183+
lua_setfield(L, -2, "get_pp_info");
1184+
1185+
lua_pushcfunction(L, ts_lua_client_request_get_pp_info_int);
1186+
lua_setfield(L, -2, "get_pp_info_int");
1187+
}
1188+
1189+
static int
1190+
ts_lua_client_request_get_pp_info(lua_State *L)
1191+
{
1192+
uint16_t key;
1193+
const char *value = nullptr;
1194+
int length;
1195+
ts_lua_http_ctx *http_ctx;
1196+
TSHttpSsn ssnp;
1197+
TSVConn client_conn;
1198+
1199+
GET_HTTP_CONTEXT(http_ctx, L);
1200+
1201+
key = static_cast<uint16_t>(luaL_checkinteger(L, 1));
1202+
1203+
ssnp = TSHttpTxnSsnGet(http_ctx->txnp);
1204+
client_conn = TSHttpSsnClientVConnGet(ssnp);
1205+
1206+
if (TSVConnPPInfoGet(client_conn, key, &value, &length) == TS_SUCCESS) {
1207+
if (key == TS_PP_INFO_SRC_ADDR || key == TS_PP_INFO_DST_ADDR) {
1208+
// For addresses, convert sockaddr to string
1209+
char ip_str[INET6_ADDRSTRLEN];
1210+
const sockaddr *addr = reinterpret_cast<const sockaddr *>(value);
1211+
const sockaddr_in *addr_in;
1212+
const sockaddr_in6 *addr_in6;
1213+
1214+
if (addr->sa_family == AF_INET) {
1215+
addr_in = reinterpret_cast<const sockaddr_in *>(addr);
1216+
inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str));
1217+
} else if (addr->sa_family == AF_INET6) {
1218+
addr_in6 = reinterpret_cast<const sockaddr_in6 *>(addr);
1219+
inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_str, sizeof(ip_str));
1220+
} else {
1221+
lua_pushnil(L);
1222+
return 1;
1223+
}
1224+
lua_pushstring(L, ip_str);
1225+
} else {
1226+
// For other types, return as string
1227+
lua_pushlstring(L, value, length);
1228+
}
1229+
return 1;
1230+
}
1231+
1232+
lua_pushnil(L);
1233+
return 1;
1234+
}
1235+
1236+
static int
1237+
ts_lua_client_request_get_pp_info_int(lua_State *L)
1238+
{
1239+
uint16_t key;
1240+
TSMgmtInt value;
1241+
ts_lua_http_ctx *http_ctx;
1242+
TSHttpSsn ssnp;
1243+
TSVConn client_conn;
1244+
1245+
GET_HTTP_CONTEXT(http_ctx, L);
1246+
1247+
key = static_cast<uint16_t>(luaL_checkinteger(L, 1));
1248+
1249+
ssnp = TSHttpTxnSsnGet(http_ctx->txnp);
1250+
client_conn = TSHttpSsnClientVConnGet(ssnp);
1251+
1252+
if (TSVConnPPInfoIntGet(client_conn, key, &value) == TS_SUCCESS) {
1253+
lua_pushinteger(L, value);
1254+
return 1;
1255+
}
1256+
1257+
lua_pushnil(L);
1258+
return 1;
1259+
}
1260+
11511261
static int
11521262
ts_lua_client_request_client_addr_get_verified_addr(lua_State *L)
11531263
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TEST
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'''
2+
Test Lua plugin PROXY protocol support
3+
'''
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
import os
21+
22+
Test.Summary = '''
23+
Test Lua plugin PROXY protocol API
24+
'''
25+
26+
Test.SkipUnless(Condition.PluginExists('tslua.so'),)
27+
28+
Test.ContinueOnFail = True
29+
30+
# ---- Setup Origin Server ----
31+
server = Test.MakeOriginServer("server")
32+
33+
request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
34+
response_header = {
35+
"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 4\r\n\r\n",
36+
"timestamp": "1469733493.993",
37+
"body": "TEST"
38+
}
39+
server.addResponse("sessionfile.log", request_header, response_header)
40+
41+
# ---- Setup ATS ----
42+
ts = Test.MakeATSProcess("ts", enable_proxy_protocol=True)
43+
44+
ts.Disk.remap_config.AddLine(
45+
'map / http://127.0.0.1:{0}/'.format(server.Variables.Port) + ' @plugin=tslua.so @pparam=proxy_protocol.lua')
46+
47+
ts.Setup.Copy("proxy_protocol.lua", ts.Variables.CONFIGDIR)
48+
49+
ts.Disk.records_config.update({'proxy.config.diags.debug.enabled': 1, 'proxy.config.diags.debug.tags': 'ts_lua'})
50+
51+
# ---- Test with PROXY protocol ----
52+
tr = Test.AddTestRun("Test with PROXY protocol v1")
53+
tr.Processes.Default.Command = (
54+
f"curl --haproxy-protocol --haproxy-clientip 192.168.1.100 "
55+
f"http://127.0.0.1:{ts.Variables.proxy_protocol_port}/")
56+
tr.Processes.Default.ReturnCode = 0
57+
tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
58+
tr.Processes.Default.StartBefore(ts)
59+
tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("TEST", "Response body should be received")
60+
tr.StillRunningAfter = server
61+
tr.StillRunningAfter = ts
62+
63+
# Check debug log for PROXY protocol info
64+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(r"PP-Version: 1", "PROXY protocol version should be logged")
65+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
66+
r"PP-Source: 192\.168\.1\.100", "PROXY protocol source address should be logged")
67+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(r"PP-Protocol: 2", "PROXY protocol IP family should be logged")
68+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(r"PP-SocketType: 1", "PROXY protocol socket type should be logged")
69+
70+
# ---- Test without PROXY protocol ----
71+
tr2 = Test.AddTestRun("Test without PROXY protocol")
72+
tr2.Processes.Default.Command = f"curl http://127.0.0.1:{ts.Variables.port}/"
73+
tr2.Processes.Default.ReturnCode = 0
74+
tr2.Processes.Default.Streams.stdout = Testers.ContainsExpression("TEST", "Response body should be received")
75+
tr2.StillRunningAfter = server
76+
tr2.StillRunningAfter = ts
77+
78+
# Check that PP-Not-Present is logged when PROXY protocol is not used
79+
ts.Disk.traffic_out.Content += Testers.ContainsExpression(r"PP-Version: 0", "Should log when PROXY protocol is not present")
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-- Licensed to the Apache Software Foundation (ASF) under one
2+
-- or more contributor license agreements. See the NOTICE file
3+
-- distributed with this work for additional information
4+
-- regarding copyright ownership. The ASF licenses this file
5+
-- to you under the Apache License, Version 2.0 (the
6+
-- "License"); you may not use this file except in compliance
7+
-- with the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing, software
12+
-- distributed under the License is distributed on an "AS IS" BASIS,
13+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
-- See the License for the specific language governing permissions and
15+
-- limitations under the License.
16+
17+
function do_remap()
18+
local req = ts.client_request
19+
20+
-- Get PROXY protocol version
21+
local pp_version = req.get_pp_info_int(TS_LUA_PP_INFO_VERSION)
22+
if pp_version then
23+
ts.debug(string.format("PP-Version: %d", pp_version))
24+
25+
-- Get source address and port
26+
local src_addr = req.get_pp_info(TS_LUA_PP_INFO_SRC_ADDR)
27+
local src_port = req.get_pp_info_int(TS_LUA_PP_INFO_SRC_PORT)
28+
29+
-- Get destination address and port
30+
local dst_addr = req.get_pp_info(TS_LUA_PP_INFO_DST_ADDR)
31+
local dst_port = req.get_pp_info_int(TS_LUA_PP_INFO_DST_PORT)
32+
33+
-- Get protocol and socket type
34+
local protocol = req.get_pp_info_int(TS_LUA_PP_INFO_PROTOCOL)
35+
local sock_type = req.get_pp_info_int(TS_LUA_PP_INFO_SOCK_TYPE)
36+
37+
if src_addr and src_port then
38+
ts.debug(string.format("PP-Source: %s:%d", src_addr, src_port))
39+
end
40+
41+
if dst_addr and dst_port then
42+
ts.debug(string.format("PP-Destination: %s:%d", dst_addr, dst_port))
43+
end
44+
45+
if protocol then
46+
ts.debug(string.format("PP-Protocol: %d", protocol))
47+
end
48+
49+
if sock_type then
50+
ts.debug(string.format("PP-SocketType: %d", sock_type))
51+
end
52+
53+
-- Add custom header with PP info
54+
if src_addr then
55+
ts.client_request.header['X-PP-Client-IP'] = src_addr
56+
end
57+
else
58+
ts.debug("PP-Not-Present")
59+
end
60+
61+
return 0
62+
end

0 commit comments

Comments
 (0)