Skip to content

Commit efeeffa

Browse files
authored
hrw4u/header_rewrite: Add session-scope state variables (#12989)
* hrw4u/header_rewrite: Add session-scope state variables Add SESSION-FLAG, SESSION-INT8, and SESSION-INT16 conditions and their corresponding set-session-flag, set-session-int8, and set-session-int16 operators to the header_rewrite plugin. These mirror the existing transaction-scoped state variables but persist across keep-alive requests on the same connection, using a TS_USER_ARGS_SSN slot. The condition and operator classes are parameterized with a TSUserArgType scope argument to avoid code duplication. The hrw4u transpiler adds a SESSION_VARS section for declaring session-scoped variables, and the reverse transpiler handles both scopes. Documentation and tests are included. Co-Authored-By: Craig Taylor * Address Copilot review comments Use SESSION_VARS instead of VARS for session sandbox check. Reserve user-arg slots lazily per scope in acquire_state_slot(). Fix _state_vars type annotation to include VarScope in key tuple. * Address bneradt's review: wire SESSION_VARS into kg and LSP kg_visitor.py was missing sessionVarSection dispatch in visitSection, causing hrw4u-kg to silently drop SESSION_VARS blocks. lsp/strings.py only detected VARS { ... } for declaration mode, leaving session-scoped variables without hover/type metadata in hrw4u-lsp.
1 parent a6d60b2 commit efeeffa

32 files changed

Lines changed: 580 additions & 132 deletions

doc/admin-guide/configuration/hrw4u.en.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,9 @@ TXN_CLOSE_HOOK TXN_CLOSE End of transaction
405405
=============================== ======================== ================================
406406

407407
A special section `VARS` is used to declare variables. There is no equivalent in
408-
`header_rewrite`, where you managed the variables manually.
408+
`header_rewrite`, where you managed the variables manually. Similarly,
409+
`SESSION_VARS` declares session-scoped variables that persist across all
410+
transactions on the same client connection (session).
409411

410412
Variables and State Slots
411413
^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -416,6 +418,10 @@ Each variable type has a limited number of slots available:
416418
- ``int8`` - 4 slots (0-3)
417419
- ``int16`` - 1 slot (0)
418420

421+
These limits apply independently to both ``VARS`` (transaction-scoped) and
422+
``SESSION_VARS`` (session-scoped) sections. For example, you can have 16 bool
423+
transaction variables *and* 16 bool session variables.
424+
419425
By default, slots are assigned automatically in declaration order. You can explicitly assign
420426
a slot number using the ``@`` syntax::
421427

@@ -426,6 +432,16 @@ a slot number using the ``@`` syntax::
426432
counter: int8 @2; # Explicitly use int8 slot 2
427433
}
428434

435+
SESSION_VARS {
436+
is_suspicious: bool; # Session-scoped, persists across requests
437+
penalty_level: int8; # Session-scoped 8-bit integer
438+
}
439+
440+
Transaction variables (``VARS``) are reset for each new HTTP transaction, while
441+
session variables (``SESSION_VARS``) persist for the lifetime of the client
442+
connection. Session variables are useful for tracking state across keep-alive
443+
requests, such as marking a connection as suspicious after the first bad request.
444+
429445
Explicit slot assignment is useful when you need predictable slot numbers across configurations
430446
or when integrating with existing header_rewrite rules that reference specific slot numbers. In
431447
addition, a remap configuration can use ``@PPARAM`` to set one of these slot variables explicitly

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,41 @@ There's only one such integer, and its value is returned from this condition.
862862
As such, the index, ``0``, is optional here. The initialized value of this
863863
state variable is ``0``.
864864

865+
SESSION-FLAG
866+
~~~~~~~~~~~~
867+
::
868+
869+
cond %{SESSION-FLAG:<n>}
870+
871+
This condition allows you to check the state of a session-scoped flag. The
872+
``<n>`` is the number of the flag, from 0 to 15. Unlike ``STATE-FLAG`` which
873+
is scoped to the current transaction, session flags persist across all
874+
transactions on the same client connection (session). The default value of all
875+
flags are ``false``.
876+
877+
SESSION-INT8
878+
~~~~~~~~~~~~
879+
::
880+
881+
cond %{SESSION-INT8:<n>}
882+
883+
This condition allows you to check the state of a session-scoped 8-bit unsigned
884+
integer. The ``<n>`` is the number of the integer, from 0 to 3. The current
885+
value is returned, and all 4 integers are initialized to 0. Session integers
886+
persist across all transactions on the same client connection.
887+
888+
SESSION-INT16
889+
~~~~~~~~~~~~~
890+
::
891+
892+
cond %{SESSION-INT16:<0>}
893+
894+
This condition allows you to check the state of a session-scoped 16-bit unsigned
895+
integer. There's only one such integer, and its value is returned from this
896+
condition. As such, the index, ``0``, is optional here. The initialized value
897+
is ``0``. Session integers persist across all transactions on the same client
898+
connection.
899+
865900
STATUS
866901
~~~~~~
867902
::
@@ -1304,6 +1339,42 @@ The ``<value>`` is an unsigned 16-bit integer as well, 0-65535. It can also
13041339
be a condition, in which case thevalue of the condition is used. The index,
13051340
0, is always required eventhough there is only one 16-bit integer state variable.
13061341

1342+
set-session-flag
1343+
~~~~~~~~~~~~~~~~
1344+
::
1345+
1346+
set-session-flag <n> <value>
1347+
1348+
This operator allows you to set the state of a session-scoped flag. The ``<n>``
1349+
is the number of the flag, from 0 to 15. The ``<value>`` is either ``true`` or
1350+
``false``, turning the flag on or off. Unlike ``set-state-flag``, session flags
1351+
persist across all transactions on the same client connection.
1352+
1353+
set-session-int8
1354+
~~~~~~~~~~~~~~~~
1355+
::
1356+
1357+
set-session-int8 <n> <value>
1358+
1359+
This operator allows you to set the state of a session-scoped 8-bit unsigned
1360+
integer. The ``<n>`` is the number of the integer, from 0 to 3. The ``<value>``
1361+
is an unsigned 8-bit integer, 0-255. It can also be a condition, in which case
1362+
the value of the condition is used. Session integers persist across all
1363+
transactions on the same client connection.
1364+
1365+
set-session-int16
1366+
~~~~~~~~~~~~~~~~~
1367+
::
1368+
1369+
set-session-int16 0 <value>
1370+
1371+
This operator allows you to set the state of a session-scoped 16-bit unsigned
1372+
integer. The ``<value>`` is an unsigned 16-bit integer, 0-65535. It can also
1373+
be a condition, in which case the value of the condition is used. The index,
1374+
0, is always required even though there is only one 16-bit session integer
1375+
state variable. Session integers persist across all transactions on the same
1376+
client connection.
1377+
13071378
set-status
13081379
~~~~~~~~~~
13091380
::

plugins/header_rewrite/conditions.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,9 +1654,9 @@ ConditionStateFlag::set_qualifier(const std::string &q)
16541654

16551655
_flag_ix = strtol(q.c_str(), nullptr, 10);
16561656
if (_flag_ix < 0 || _flag_ix >= NUM_STATE_FLAGS) {
1657-
TSError("[%s] STATE-FLAG index out of range: %s", PLUGIN_NAME, q.c_str());
1657+
TSError("[%s] %s-FLAG index out of range: %s", PLUGIN_NAME, _scope_label(_scope), q.c_str());
16581658
} else {
1659-
Dbg(pi_dbg_ctl, "\tParsing %%{STATE-FLAG:%s}", q.c_str());
1659+
Dbg(pi_dbg_ctl, "\tParsing %%{%s-FLAG:%s}", _scope_label(_scope), q.c_str());
16601660
_mask = 1ULL << _flag_ix;
16611661
}
16621662
}
@@ -1665,15 +1665,15 @@ void
16651665
ConditionStateFlag::append_value(std::string &s, const Resources &res)
16661666
{
16671667
s += eval(res) ? "TRUE" : "FALSE";
1668-
Dbg(pi_dbg_ctl, "Evaluating STATE-FLAG(%d)", _flag_ix);
1668+
Dbg(pi_dbg_ctl, "Evaluating %s-FLAG(%d)", _scope_label(_scope), _flag_ix);
16691669
}
16701670

16711671
bool
16721672
ConditionStateFlag::eval(const Resources &res)
16731673
{
1674-
auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot));
1674+
auto data = _get_state_data(_scope, res);
16751675

1676-
Dbg(pi_dbg_ctl, "Evaluating STATE-FLAG()");
1676+
Dbg(pi_dbg_ctl, "Evaluating %s-FLAG()", _scope_label(_scope));
16771677

16781678
return (data & _mask) == _mask;
16791679
}
@@ -1696,9 +1696,9 @@ ConditionStateInt8::set_qualifier(const std::string &q)
16961696

16971697
_byte_ix = strtol(q.c_str(), nullptr, 10);
16981698
if (_byte_ix < 0 || _byte_ix >= NUM_STATE_INT8S) {
1699-
TSError("[%s] STATE-INT8 index out of range: %s", PLUGIN_NAME, q.c_str());
1699+
TSError("[%s] %s-INT8 index out of range: %s", PLUGIN_NAME, _scope_label(_scope), q.c_str());
17001700
} else {
1701-
Dbg(pi_dbg_ctl, "\tParsing %%{STATE-INT8:%s}", q.c_str());
1701+
Dbg(pi_dbg_ctl, "\tParsing %%{%s-INT8:%s}", _scope_label(_scope), q.c_str());
17021702
}
17031703
}
17041704

@@ -1709,15 +1709,15 @@ ConditionStateInt8::append_value(std::string &s, const Resources &res)
17091709

17101710
s += std::to_string(data);
17111711

1712-
Dbg(pi_dbg_ctl, "Appending STATE-INT8(%d) to evaluation value -> %s", data, s.c_str());
1712+
Dbg(pi_dbg_ctl, "Appending %s-INT8(%d) to evaluation value -> %s", _scope_label(_scope), data, s.c_str());
17131713
}
17141714

17151715
bool
17161716
ConditionStateInt8::eval(const Resources &res)
17171717
{
17181718
uint8_t data = _get_data(res);
17191719

1720-
Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
1720+
Dbg(pi_dbg_ctl, "Evaluating %s-INT8()", _scope_label(_scope));
17211721

17221722
return static_cast<const MatcherType *>(_matcher.get())->test(data, res);
17231723
}
@@ -1742,9 +1742,9 @@ ConditionStateInt16::set_qualifier(const std::string &q)
17421742
long ix = strtol(q.c_str(), nullptr, 10);
17431743

17441744
if (ix != 0) {
1745-
TSError("[%s] STATE-INT16 index out of range: %s", PLUGIN_NAME, q.c_str());
1745+
TSError("[%s] %s-INT16 index out of range: %s", PLUGIN_NAME, _scope_label(_scope), q.c_str());
17461746
} else {
1747-
Dbg(pi_dbg_ctl, "\tParsing %%{STATE-INT16:%s}", q.c_str());
1747+
Dbg(pi_dbg_ctl, "\tParsing %%{%s-INT16:%s}", _scope_label(_scope), q.c_str());
17481748
}
17491749
}
17501750
}
@@ -1755,15 +1755,15 @@ ConditionStateInt16::append_value(std::string &s, const Resources &res)
17551755
uint16_t data = _get_data(res);
17561756

17571757
s += std::to_string(data);
1758-
Dbg(pi_dbg_ctl, "Appending STATE-INT16(%d) to evaluation value -> %s", data, s.c_str());
1758+
Dbg(pi_dbg_ctl, "Appending %s-INT16(%d) to evaluation value -> %s", _scope_label(_scope), data, s.c_str());
17591759
}
17601760

17611761
bool
17621762
ConditionStateInt16::eval(const Resources &res)
17631763
{
17641764
uint16_t data = _get_data(res);
17651765

1766-
Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
1766+
Dbg(pi_dbg_ctl, "Evaluating %s-INT16()", _scope_label(_scope));
17671767

17681768
return static_cast<const MatcherType *>(_matcher.get())->test(data, res);
17691769
}

plugins/header_rewrite/conditions.h

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -800,17 +800,17 @@ class ConditionGroup : public Condition
800800
bool _end = false;
801801
};
802802

803-
// State Flags
803+
// State/Session Flags (parameterized by scope)
804804
class ConditionStateFlag : public Condition
805805
{
806806
using SelfType = ConditionStateFlag;
807807
// No matcher for this, it's all easy peasy
808808

809809
public:
810-
explicit ConditionStateFlag()
810+
explicit ConditionStateFlag(TSUserArgType scope = TS_USER_ARGS_TXN) : _scope(scope)
811811
{
812812
static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
813-
Dbg(dbg_ctl, "Calling CTOR for ConditionStateFlag");
813+
Dbg(dbg_ctl, "Calling CTOR for ConditionStateFlag (scope=%s)", _scope_label(_scope));
814814
}
815815

816816
// noncopyable
@@ -826,26 +826,33 @@ class ConditionStateFlag : public Condition
826826
bool
827827
need_txn_slot() const override
828828
{
829-
return true;
829+
return _scope == TS_USER_ARGS_TXN;
830+
}
831+
832+
bool
833+
need_ssn_slot() const override
834+
{
835+
return _scope == TS_USER_ARGS_SSN;
830836
}
831837

832838
private:
833-
int _flag_ix = -1;
834-
uint64_t _mask = 0;
839+
TSUserArgType _scope = TS_USER_ARGS_TXN;
840+
int _flag_ix = -1;
841+
uint64_t _mask = 0;
835842
};
836843

837-
// INT8 state variables
844+
// State/Session INT8 variables (parameterized by scope)
838845
class ConditionStateInt8 : public Condition
839846
{
840847
using DataType = uint8_t;
841848
using MatcherType = Matchers<DataType>;
842849
using SelfType = ConditionStateInt8;
843850

844851
public:
845-
explicit ConditionStateInt8()
852+
explicit ConditionStateInt8(TSUserArgType scope = TS_USER_ARGS_TXN) : _scope(scope)
846853
{
847854
static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
848-
Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt8");
855+
Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt8 (scope=%s)", _scope_label(_scope));
849856
}
850857

851858
// noncopyable
@@ -862,36 +869,42 @@ class ConditionStateInt8 : public Condition
862869
bool
863870
need_txn_slot() const override
864871
{
865-
return true;
872+
return _scope == TS_USER_ARGS_TXN;
873+
}
874+
875+
bool
876+
need_ssn_slot() const override
877+
{
878+
return _scope == TS_USER_ARGS_SSN;
866879
}
867880

868881
private:
869-
// Little helper function to extract out the data from the TXN user pointer
870882
uint8_t
871883
_get_data(const Resources &res) const
872884
{
873885
TSAssert(_byte_ix >= 0 && _byte_ix < NUM_STATE_INT8S);
874-
auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot));
886+
auto ptr = _get_state_data(_scope, res);
875887
uint8_t data = (ptr & STATE_INT8_MASKS[_byte_ix]) >> (NUM_STATE_FLAGS + _byte_ix * 8);
876888

877889
return data;
878890
}
879891

880-
int _byte_ix = -1;
892+
TSUserArgType _scope = TS_USER_ARGS_TXN;
893+
int _byte_ix = -1;
881894
};
882895

883-
// INT16 state variables
896+
// State/Session INT16 variables (parameterized by scope)
884897
class ConditionStateInt16 : public Condition
885898
{
886899
using DataType = uint16_t;
887900
using MatcherType = Matchers<DataType>;
888901
using SelfType = ConditionStateInt16;
889902

890903
public:
891-
explicit ConditionStateInt16()
904+
explicit ConditionStateInt16(TSUserArgType scope = TS_USER_ARGS_TXN) : _scope(scope)
892905
{
893906
static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit system.");
894-
Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt16");
907+
Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt16 (scope=%s)", _scope_label(_scope));
895908
}
896909

897910
// noncopyable
@@ -908,18 +921,25 @@ class ConditionStateInt16 : public Condition
908921
bool
909922
need_txn_slot() const override
910923
{
911-
return true;
924+
return _scope == TS_USER_ARGS_TXN;
925+
}
926+
927+
bool
928+
need_ssn_slot() const override
929+
{
930+
return _scope == TS_USER_ARGS_SSN;
912931
}
913932

914933
private:
915-
// Little helper function to extract out the data from the TXN user pointer
916934
uint16_t
917935
_get_data(const Resources &res) const
918936
{
919-
auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, _txn_slot));
937+
auto ptr = _get_state_data(_scope, res);
920938

921939
return ((ptr & STATE_INT16_MASK) >> 48);
922940
}
941+
942+
TSUserArgType _scope = TS_USER_ARGS_TXN;
923943
};
924944

925945
// Last regex capture

plugins/header_rewrite/factory.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ operator_factory(const std::string &op)
8787
o = new OperatorSetStateInt8();
8888
} else if (op == "set-state-int16") {
8989
o = new OperatorSetStateInt16();
90+
} else if (op == "set-session-flag") {
91+
o = new OperatorSetStateFlag(TS_USER_ARGS_SSN);
92+
} else if (op == "set-session-int8") {
93+
o = new OperatorSetStateInt8(TS_USER_ARGS_SSN);
94+
} else if (op == "set-session-int16") {
95+
o = new OperatorSetStateInt16(TS_USER_ARGS_SSN);
9096
} else if (op == "set-effective-address") {
9197
o = new OperatorSetEffectiveAddress();
9298
} else if (op == "set-next-hop-strategy") {
@@ -191,6 +197,12 @@ condition_factory(const std::string &cond)
191197
c = new ConditionStateInt8();
192198
} else if (c_name == "STATE-INT16") {
193199
c = new ConditionStateInt16();
200+
} else if (c_name == "SESSION-FLAG") {
201+
c = new ConditionStateFlag(TS_USER_ARGS_SSN);
202+
} else if (c_name == "SESSION-INT8") {
203+
c = new ConditionStateInt8(TS_USER_ARGS_SSN);
204+
} else if (c_name == "SESSION-INT16") {
205+
c = new ConditionStateInt16(TS_USER_ARGS_SSN);
194206
} else if (c_name == "LAST-CAPTURE") {
195207
c = new ConditionLastCapture();
196208
} else {

0 commit comments

Comments
 (0)