Coverage Report

Created: 2026-01-03 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/action.c
Line
Count
Source
1
/*
2
 * Action management functions.
3
 *
4
 * Copyright 2017 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version
9
 * 2 of the License, or (at your option) any later version.
10
 *
11
 */
12
13
#include <haproxy/acl.h>
14
#include <haproxy/action.h>
15
#include <haproxy/api.h>
16
#include <haproxy/cfgparse.h>
17
#include <haproxy/errors.h>
18
#include <haproxy/list.h>
19
#include <haproxy/obj_type.h>
20
#include <haproxy/pool.h>
21
#include <haproxy/proxy.h>
22
#include <haproxy/stick_table.h>
23
#include <haproxy/task.h>
24
#include <haproxy/tools.h>
25
26
27
/* Check an action ruleset validity. It returns the number of error encountered
28
 * and err_code is updated if a warning is emitted.
29
 */
30
int check_action_rules(struct list *rules, struct proxy *px, int *err_code)
31
0
{
32
0
  struct act_rule *rule;
33
0
  char *errmsg = NULL;
34
0
  int err = 0;
35
36
0
  list_for_each_entry(rule, rules, list) {
37
0
    if (rule->check_ptr && !rule->check_ptr(rule, px, &errmsg)) {
38
0
      ha_alert("Proxy '%s': %s.\n", px->id, errmsg);
39
0
      err++;
40
0
    }
41
0
    *err_code |= warnif_tcp_http_cond(px, rule->cond);
42
0
    ha_free(&errmsg);
43
0
  }
44
45
0
  return err;
46
0
}
47
48
/* Find and check the target table used by an action track-sc*. This
49
 * function should be called during the configuration validity check.
50
 *
51
 * The function returns 1 in success case, otherwise, it returns 0 and err is
52
 * filled.
53
 */
54
int check_trk_action(struct act_rule *rule, struct proxy *px, char **err)
55
0
{
56
0
  struct stktable *target;
57
58
0
  if (rule->arg.trk_ctr.table.n)
59
0
    target = stktable_find_by_name(rule->arg.trk_ctr.table.n);
60
0
  else
61
0
    target = px->table;
62
63
0
  if (!target) {
64
0
    memprintf(err, "unable to find table '%s' referenced by track-sc%d",
65
0
        rule->arg.trk_ctr.table.n ?  rule->arg.trk_ctr.table.n : px->id,
66
0
        rule->action);
67
0
    return 0;
68
0
  }
69
70
0
  if (!stktable_compatible_sample(rule->arg.trk_ctr.expr,  target->type)) {
71
0
    memprintf(err, "stick-table '%s' uses a type incompatible with the 'track-sc%d' rule",
72
0
        rule->arg.trk_ctr.table.n ? rule->arg.trk_ctr.table.n : px->id,
73
0
        rule->action);
74
0
    return 0;
75
0
  }
76
0
  else {
77
0
    if (!in_proxies_list(target->proxies_list, px)) {
78
0
      px->next_stkt_ref = target->proxies_list;
79
0
      target->proxies_list = px;
80
0
    }
81
0
    free(rule->arg.trk_ctr.table.n);
82
0
    rule->arg.trk_ctr.table.t = target;
83
    /* Note: if we decide to enhance the track-sc syntax, we may be
84
     * able to pass a list of counters to track and allocate them
85
     * right here using stktable_alloc_data_type().
86
     */
87
0
  }
88
89
0
  if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
90
0
    if (!px->tcp_req.inspect_delay && !(rule->arg.trk_ctr.expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
91
0
      ha_warning("%s '%s' : a 'tcp-request content track-sc*' rule explicitly depending on request"
92
0
           " contents without any 'tcp-request inspect-delay' setting."
93
0
           " This means that this rule will randomly find its contents. This can be fixed by"
94
0
           " setting the tcp-request inspect-delay.\n",
95
0
           proxy_type_str(px), px->id);
96
0
    }
97
98
    /* The following warning is emitted because HTTP multiplexers are able to catch errors
99
     * or timeouts at the session level, before instantiating any stream.
100
     * Thus the tcp-request content ruleset will not be evaluated in such case. It means,
101
     * http_req and http_err counters will not be incremented as expected, even if the tracked
102
     * counter does not use the request content. To track invalid requests it should be
103
     * performed at the session level using a tcp-request session rule.
104
     */
105
0
    if (px->mode == PR_MODE_HTTP &&
106
0
        !(rule->arg.trk_ctr.expr->fetch->use & (SMP_USE_L6REQ|SMP_USE_HRQHV|SMP_USE_HRQHP|SMP_USE_HRQBO)) &&
107
0
        (!rule->cond || !(rule->cond->use & (SMP_USE_L6REQ|SMP_USE_HRQHV|SMP_USE_HRQHP|SMP_USE_HRQBO)))) {
108
0
      ha_warning("%s '%s' : a 'tcp-request content track-sc*' rule not depending on request"
109
0
           " contents for an HTTP frontend should be executed at the session level, using a"
110
0
           " 'tcp-request session' rule (mandatory to track invalid HTTP requests).\n",
111
0
           proxy_type_str(px), px->id);
112
0
    }
113
0
  }
114
115
0
  return 1;
116
0
}
117
118
/* check a capture rule. This function should be called during the configuration
119
 * validity check.
120
 *
121
 * The function returns 1 in success case, otherwise, it returns 0 and err is
122
 * filled.
123
 */
124
int check_capture(struct act_rule *rule, struct proxy *px, char **err)
125
0
{
126
0
  if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE) && !px->tcp_req.inspect_delay &&
127
0
      !(rule->arg.cap.expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
128
0
    ha_warning("%s '%s' : a 'tcp-request capture' rule explicitly depending on request"
129
0
         " contents without any 'tcp-request inspect-delay' setting."
130
0
         " This means that this rule will randomly find its contents. This can be fixed by"
131
0
         " setting the tcp-request inspect-delay.\n",
132
0
         proxy_type_str(px), px->id);
133
0
  }
134
135
0
  return 1;
136
0
}
137
138
int act_resolution_cb(struct resolv_requester *requester, struct dns_counters *counters)
139
0
{
140
0
  struct stream *stream;
141
142
0
  if (requester->resolution == NULL)
143
0
    return 0;
144
145
0
  stream = objt_stream(requester->owner);
146
0
  if (stream == NULL)
147
0
    return 0;
148
149
0
  task_wakeup(stream->task, TASK_WOKEN_MSG);
150
151
0
  return 0;
152
0
}
153
154
/*
155
 * Do resolve error management callback
156
 * returns:
157
 *  0 if we can trash answser items.
158
 *  1 when safely ignored and we must kept answer items
159
 */
160
int act_resolution_error_cb(struct resolv_requester *requester, int error_code)
161
0
{
162
0
  struct stream *stream;
163
164
0
  if (requester->resolution == NULL)
165
0
    return 0;
166
167
0
  stream = objt_stream(requester->owner);
168
0
  if (stream == NULL)
169
0
    return 0;
170
171
0
  task_wakeup(stream->task, TASK_WOKEN_MSG);
172
173
0
  return 0;
174
0
}
175
176
/* Parse a set-timeout rule statement. It first checks if the timeout name is
177
 * valid and proxy is capable of handling it, and returns it in <rule->arg.timeout.type>.
178
 * Then the timeout is parsed as a plain value and * returned in <rule->arg.timeout.value>.
179
 * If there is a parsing error, the value is reparsed as an expression and
180
 * returned in <rule->arg.timeout.expr>.
181
 *
182
 * Returns -1 if the name is invalid or neither a time or an expression can be
183
 * parsed, or if the timeout value is 0.
184
 */
185
int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule,
186
             struct proxy *px, char **err)
187
0
{
188
0
  const char *res;
189
0
  const char *timeout_name = args[idx++];
190
191
0
  if (strcmp(timeout_name, "server") == 0) {
192
0
    if (!(px->cap & PR_CAP_BE)) {
193
0
      memprintf(err, "'%s' has no backend capability", px->id);
194
0
      return -1;
195
0
    }
196
0
    rule->arg.timeout.type = ACT_TIMEOUT_SERVER;
197
0
  }
198
0
  else if (strcmp(timeout_name, "tunnel") == 0) {
199
0
    if (!(px->cap & PR_CAP_BE)) {
200
0
      memprintf(err, "'%s' has no backend capability", px->id);
201
0
      return -1;
202
0
    }
203
0
    rule->arg.timeout.type = ACT_TIMEOUT_TUNNEL;
204
0
  }
205
0
  else if (strcmp(timeout_name, "client") == 0) {
206
0
    if (!(px->cap & PR_CAP_FE)) {
207
0
      memprintf(err, "'%s' has no frontend capability", px->id);
208
0
      return -1;
209
0
    }
210
0
    rule->arg.timeout.type = ACT_TIMEOUT_CLIENT;
211
0
  }
212
0
  else {
213
0
    memprintf(err,
214
0
              "'set-timeout' rule supports 'server'/'tunnel'/'client' (got '%s')",
215
0
              timeout_name);
216
0
    return -1;
217
0
  }
218
219
0
  res = parse_time_err(args[idx], (unsigned int *)&rule->arg.timeout.value, TIME_UNIT_MS);
220
0
  if (res == PARSE_TIME_OVER) {
221
0
    memprintf(err, "timer overflow in argument '%s' to rule 'set-timeout %s' (maximum value is 2147483647 ms or ~24.8 days)",
222
0
        args[idx], timeout_name);
223
0
    return -1;
224
0
  }
225
0
  else if (res == PARSE_TIME_UNDER) {
226
0
    memprintf(err, "timer underflow in argument '%s' to rule 'set-timeout %s' (minimum value is 1 ms)",
227
0
        args[idx], timeout_name);
228
0
    return -1;
229
0
  }
230
  /* res not NULL, parsing error */
231
0
  else if (res) {
232
0
    rule->arg.timeout.expr = sample_parse_expr((char **)args, &idx, px->conf.args.file,
233
0
                 px->conf.args.line, err, &px->conf.args, NULL);
234
0
    if (!rule->arg.timeout.expr) {
235
0
      memprintf(err, "unexpected character '%c' in rule 'set-timeout %s'", *res, timeout_name);
236
0
      return -1;
237
0
    }
238
0
  }
239
  /* res NULL, parsing ok but value is 0 */
240
0
  else if (!(rule->arg.timeout.value)) {
241
0
    memprintf(err, "null value is not valid for a 'set-timeout %s' rule",
242
0
        timeout_name);
243
0
    return -1;
244
0
  }
245
246
0
  return 0;
247
0
}
248
249
/* tries to find in list <keywords> a similar looking action as the one in
250
 * <word>, and returns it otherwise NULL. <word> may be NULL or empty. An
251
 * optional array of extra words to compare may be passed in <extra>, but it
252
 * must then be terminated by a NULL entry. If unused it may be NULL.
253
 */
254
const char *action_suggest(const char *word, const struct list *keywords, const char **extra)
255
0
{
256
0
  uint8_t word_sig[1024];
257
0
  uint8_t list_sig[1024];
258
0
  const struct action_kw_list *kwl;
259
0
  const struct action_kw *best_kw = NULL;
260
0
  const char *best_ptr = NULL;
261
0
  int dist, best_dist = INT_MAX;
262
0
  int index;
263
264
0
  if (!word || !*word)
265
0
    return NULL;
266
267
0
  make_word_fingerprint(word_sig, word);
268
0
  list_for_each_entry(kwl, keywords, list) {
269
0
    for (index = 0; kwl->kw[index].kw != NULL; index++) {
270
0
      make_word_fingerprint(list_sig, kwl->kw[index].kw);
271
0
      dist = word_fingerprint_distance(word_sig, list_sig);
272
0
      if (dist < best_dist) {
273
0
        best_dist = dist;
274
0
        best_kw   = &kwl->kw[index];
275
0
        best_ptr  = best_kw->kw;
276
0
      }
277
0
    }
278
0
  }
279
280
0
  while (extra && *extra) {
281
0
    make_word_fingerprint(list_sig, *extra);
282
0
    dist = word_fingerprint_distance(word_sig, list_sig);
283
0
    if (dist < best_dist) {
284
0
      best_dist = dist;
285
0
      best_kw   = NULL;
286
0
      best_ptr  = *extra;
287
0
    }
288
0
    extra++;
289
0
  }
290
291
  /* eliminate too different ones, with more tolerance for prefixes
292
   * when they're known to exist (not from extra list).
293
   */
294
0
  if (best_ptr &&
295
0
      (best_dist > (2 + (best_kw && (best_kw->flags & KWF_MATCH_PREFIX))) * strlen(word) ||
296
0
       best_dist > (2 + (best_kw && (best_kw->flags & KWF_MATCH_PREFIX))) * strlen(best_ptr)))
297
0
    best_ptr = NULL;
298
299
0
  return best_ptr;
300
0
}
301
302
/* allocates a rule for ruleset <from> (ACT_F_*), from file name <file> and
303
 * line <linenum>. <file> and <linenum> may be zero if unknown. Returns the
304
 * rule, otherwise NULL in case of memory allocation error.
305
 */
306
struct act_rule *new_act_rule(enum act_from from, const char *file, int linenum)
307
0
{
308
0
  struct act_rule *rule;
309
310
0
  rule = calloc(1, sizeof(*rule));
311
0
  if (!rule)
312
0
    return NULL;
313
0
  rule->from = from;
314
0
  rule->conf.file = file ? strdup(file) : NULL;
315
0
  rule->conf.line = linenum;
316
0
  LIST_INIT(&rule->list);
317
0
  return rule;
318
0
}
319
320
/* fees rule <rule> and its elements as well as the condition */
321
void free_act_rule(struct act_rule *rule)
322
0
{
323
0
  LIST_DELETE(&rule->list);
324
0
  free_acl_cond(rule->cond);
325
0
  if (rule->release_ptr)
326
0
    rule->release_ptr(rule);
327
0
  free(rule->conf.file);
328
0
  free(rule);
329
0
}
330
331
void free_act_rules(struct list *rules)
332
0
{
333
0
  struct act_rule *rule, *ruleb;
334
335
0
  list_for_each_entry_safe(rule, ruleb, rules, list) {
336
0
    free_act_rule(rule);
337
0
  }
338
0
}
339
340
/* dumps all known actions registered in action rules <rules> after prefix
341
 * <pfx> to stdout. The actions are alphabetically sorted. Those with the
342
 * KWF_MATCH_PREFIX flag have their name suffixed with '*'.
343
 */
344
void dump_act_rules(const struct list *rules, const char *pfx)
345
0
{
346
0
  const struct action_kw *akwp, *akwn;
347
0
  struct action_kw_list *akwl;
348
0
  int index;
349
350
0
  for (akwn = akwp = NULL;; akwp = akwn) {
351
0
    list_for_each_entry(akwl, rules, list) {
352
0
      for (index = 0; akwl->kw[index].kw != NULL; index++)
353
0
        if (strordered(akwp ? akwp->kw : NULL,
354
0
                 akwl->kw[index].kw,
355
0
                 akwn != akwp ? akwn->kw : NULL))
356
0
          akwn = &akwl->kw[index];
357
0
    }
358
0
    if (akwn == akwp)
359
0
      break;
360
0
    printf("%s%s%s\n", pfx ? pfx : "", akwn->kw,
361
0
           (akwn->flags & KWF_MATCH_PREFIX) ? "*" : "");
362
0
  }
363
0
}