blob: 564b1eaa452ad21ede517c6d8de793c3c1b29fff [file] [log] [blame]
Willy Tarreau39713102016-11-25 15:49:32 +01001/*
2 * "tcp" rules processing
3 *
4 * Copyright 2000-2016 Willy Tarreau <w@1wt.eu>
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#include <common/cfgparse.h>
13#include <common/compat.h>
14#include <common/config.h>
15#include <common/debug.h>
16#include <common/mini-clist.h>
17#include <common/standard.h>
18#include <common/ticks.h>
19#include <common/time.h>
20
21#include <types/arg.h>
22#include <types/capture.h>
23#include <types/connection.h>
24#include <types/global.h>
25
26#include <proto/acl.h>
27#include <proto/action.h>
28#include <proto/channel.h>
29#include <proto/connection.h>
30#include <proto/log.h>
31#include <proto/proxy.h>
32#include <proto/sample.h>
33#include <proto/stick_table.h>
34#include <proto/stream.h>
35#include <proto/stream_interface.h>
36#include <proto/tcp_rules.h>
37
38/* List head of all known action keywords for "tcp-request connection" */
39struct list tcp_req_conn_keywords = LIST_HEAD_INIT(tcp_req_conn_keywords);
40struct list tcp_req_sess_keywords = LIST_HEAD_INIT(tcp_req_sess_keywords);
41struct list tcp_req_cont_keywords = LIST_HEAD_INIT(tcp_req_cont_keywords);
42struct list tcp_res_cont_keywords = LIST_HEAD_INIT(tcp_res_cont_keywords);
43
44/*
45 * Register keywords.
46 */
47void tcp_req_conn_keywords_register(struct action_kw_list *kw_list)
48{
49 LIST_ADDQ(&tcp_req_conn_keywords, &kw_list->list);
50}
51
52void tcp_req_sess_keywords_register(struct action_kw_list *kw_list)
53{
54 LIST_ADDQ(&tcp_req_sess_keywords, &kw_list->list);
55}
56
57void tcp_req_cont_keywords_register(struct action_kw_list *kw_list)
58{
59 LIST_ADDQ(&tcp_req_cont_keywords, &kw_list->list);
60}
61
62void tcp_res_cont_keywords_register(struct action_kw_list *kw_list)
63{
64 LIST_ADDQ(&tcp_res_cont_keywords, &kw_list->list);
65}
66
67/*
68 * Return the struct tcp_req_action_kw associated to a keyword.
69 */
70static struct action_kw *tcp_req_conn_action(const char *kw)
71{
72 return action_lookup(&tcp_req_conn_keywords, kw);
73}
74
75static struct action_kw *tcp_req_sess_action(const char *kw)
76{
77 return action_lookup(&tcp_req_sess_keywords, kw);
78}
79
80static struct action_kw *tcp_req_cont_action(const char *kw)
81{
82 return action_lookup(&tcp_req_cont_keywords, kw);
83}
84
85static struct action_kw *tcp_res_cont_action(const char *kw)
86{
87 return action_lookup(&tcp_res_cont_keywords, kw);
88}
89
90/* This function performs the TCP request analysis on the current request. It
91 * returns 1 if the processing can continue on next analysers, or zero if it
92 * needs more data, encounters an error, or wants to immediately abort the
93 * request. It relies on buffers flags, and updates s->req->analysers. The
94 * function may be called for frontend rules and backend rules. It only relies
95 * on the backend pointer so this works for both cases.
96 */
97int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit)
98{
99 struct session *sess = s->sess;
100 struct act_rule *rule;
101 struct stksess *ts;
102 struct stktable *t;
103 int partial;
104 int act_flags = 0;
105
Christopher Faulet45073512018-07-20 10:16:29 +0200106 DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n",
Willy Tarreau39713102016-11-25 15:49:32 +0100107 now_ms, __FUNCTION__,
108 s,
109 req,
110 req->rex, req->wex,
111 req->flags,
Christopher Faulet45073512018-07-20 10:16:29 +0200112 ci_data(req),
Willy Tarreau39713102016-11-25 15:49:32 +0100113 req->analysers);
114
115 /* We don't know whether we have enough data, so must proceed
116 * this way :
117 * - iterate through all rules in their declaration order
118 * - if one rule returns MISS, it means the inspect delay is
119 * not over yet, then return immediately, otherwise consider
120 * it as a non-match.
121 * - if one rule returns OK, then return OK
122 * - if one rule returns KO, then return KO
123 */
124
Willy Tarreau23752332018-06-15 14:54:53 +0200125 if ((req->flags & CF_SHUTR) || channel_full(req, global.tune.maxrewrite) ||
Willy Tarreau39713102016-11-25 15:49:32 +0100126 !s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms))
127 partial = SMP_OPT_FINAL;
128 else
129 partial = 0;
130
131 /* If "the current_rule_list" match the executed rule list, we are in
132 * resume condition. If a resume is needed it is always in the action
133 * and never in the ACL or converters. In this case, we initialise the
134 * current rule, and go to the action execution point.
135 */
136 if (s->current_rule) {
137 rule = s->current_rule;
138 s->current_rule = NULL;
139 if (s->current_rule_list == &s->be->tcp_req.inspect_rules)
140 goto resume_execution;
141 }
142 s->current_rule_list = &s->be->tcp_req.inspect_rules;
143
144 list_for_each_entry(rule, &s->be->tcp_req.inspect_rules, list) {
145 enum acl_test_res ret = ACL_TEST_PASS;
146
147 if (rule->cond) {
148 ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_REQ | partial);
149 if (ret == ACL_TEST_MISS)
150 goto missing_data;
151
152 ret = acl_pass(ret);
153 if (rule->cond->pol == ACL_COND_UNLESS)
154 ret = !ret;
155 }
156
157 if (ret) {
158 act_flags |= ACT_FLAG_FIRST;
159resume_execution:
160 /* we have a matching rule. */
161 if (rule->action == ACT_ACTION_ALLOW) {
162 break;
163 }
164 else if (rule->action == ACT_ACTION_DENY) {
165 channel_abort(req);
166 channel_abort(&s->res);
167 req->analysers = 0;
168
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200169 HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
170 HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreaua12dde02016-12-22 18:14:41 +0100171 if (sess->listener && sess->listener->counters)
Christopher Faulet8d8aa0d2017-05-30 15:36:50 +0200172 HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau39713102016-11-25 15:49:32 +0100173
174 if (!(s->flags & SF_ERR_MASK))
175 s->flags |= SF_ERR_PRXCOND;
176 if (!(s->flags & SF_FINST_MASK))
177 s->flags |= SF_FINST_R;
178 return 0;
179 }
180 else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
181 /* Note: only the first valid tracking parameter of each
182 * applies.
183 */
184 struct stktable_key *key;
185 struct sample smp;
186
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200187 if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]))
Willy Tarreau39713102016-11-25 15:49:32 +0100188 continue;
189
190 t = rule->arg.trk_ctr.table.t;
191 key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.trk_ctr.expr, &smp);
192
193 if ((smp.flags & SMP_F_MAY_CHANGE) && !(partial & SMP_OPT_FINAL))
194 goto missing_data; /* key might appear later */
195
196 if (key && (ts = stktable_get_entry(t, key))) {
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200197 stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts);
198 stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
Willy Tarreau39713102016-11-25 15:49:32 +0100199 if (sess->fe != s->be)
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200200 stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
Willy Tarreau39713102016-11-25 15:49:32 +0100201 }
202 }
203 else if (rule->action == ACT_TCP_CAPTURE) {
204 struct sample *key;
205 struct cap_hdr *h = rule->arg.cap.hdr;
206 char **cap = s->req_cap;
207 int len;
208
209 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.cap.expr, SMP_T_STR);
210 if (!key)
211 continue;
212
213 if (key->flags & SMP_F_MAY_CHANGE)
214 goto missing_data;
215
216 if (cap[h->index] == NULL)
Willy Tarreaubafbe012017-11-24 17:34:44 +0100217 cap[h->index] = pool_alloc(h->pool);
Willy Tarreau39713102016-11-25 15:49:32 +0100218
219 if (cap[h->index] == NULL) /* no more capture memory */
220 continue;
221
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200222 len = key->data.u.str.data;
Willy Tarreau39713102016-11-25 15:49:32 +0100223 if (len > h->len)
224 len = h->len;
225
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200226 memcpy(cap[h->index], key->data.u.str.area,
227 len);
Willy Tarreau39713102016-11-25 15:49:32 +0100228 cap[h->index][len] = 0;
229 }
230 else {
231 /* Custom keywords. */
232 if (!rule->action_ptr)
233 continue;
234
235 if (partial & SMP_OPT_FINAL)
236 act_flags |= ACT_FLAG_FINAL;
237
238 switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
239 case ACT_RET_ERR:
240 case ACT_RET_CONT:
241 continue;
242 case ACT_RET_STOP:
243 break;
244 case ACT_RET_YIELD:
245 s->current_rule = rule;
246 goto missing_data;
247 }
248 break; /* ACT_RET_STOP */
249 }
250 }
251 }
252
253 /* if we get there, it means we have no rule which matches, or
254 * we have an explicit accept, so we apply the default accept.
255 */
256 req->analysers &= ~an_bit;
257 req->analyse_exp = TICK_ETERNITY;
258 return 1;
259
260 missing_data:
261 channel_dont_connect(req);
262 /* just set the request timeout once at the beginning of the request */
263 if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay)
264 req->analyse_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay);
265 return 0;
266
267}
268
269/* This function performs the TCP response analysis on the current response. It
270 * returns 1 if the processing can continue on next analysers, or zero if it
271 * needs more data, encounters an error, or wants to immediately abort the
272 * response. It relies on buffers flags, and updates s->rep->analysers. The
273 * function may be called for backend rules.
274 */
275int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit)
276{
277 struct session *sess = s->sess;
278 struct act_rule *rule;
279 int partial;
280 int act_flags = 0;
281
Christopher Faulet45073512018-07-20 10:16:29 +0200282 DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n",
Willy Tarreau39713102016-11-25 15:49:32 +0100283 now_ms, __FUNCTION__,
284 s,
285 rep,
286 rep->rex, rep->wex,
287 rep->flags,
Christopher Faulet45073512018-07-20 10:16:29 +0200288 ci_data(rep),
Willy Tarreau39713102016-11-25 15:49:32 +0100289 rep->analysers);
290
291 /* We don't know whether we have enough data, so must proceed
292 * this way :
293 * - iterate through all rules in their declaration order
294 * - if one rule returns MISS, it means the inspect delay is
295 * not over yet, then return immediately, otherwise consider
296 * it as a non-match.
297 * - if one rule returns OK, then return OK
298 * - if one rule returns KO, then return KO
299 */
300
301 if (rep->flags & CF_SHUTR || tick_is_expired(rep->analyse_exp, now_ms))
302 partial = SMP_OPT_FINAL;
303 else
304 partial = 0;
305
306 /* If "the current_rule_list" match the executed rule list, we are in
307 * resume condition. If a resume is needed it is always in the action
308 * and never in the ACL or converters. In this case, we initialise the
309 * current rule, and go to the action execution point.
310 */
311 if (s->current_rule) {
312 rule = s->current_rule;
313 s->current_rule = NULL;
314 if (s->current_rule_list == &s->be->tcp_rep.inspect_rules)
315 goto resume_execution;
316 }
317 s->current_rule_list = &s->be->tcp_rep.inspect_rules;
318
319 list_for_each_entry(rule, &s->be->tcp_rep.inspect_rules, list) {
320 enum acl_test_res ret = ACL_TEST_PASS;
321
322 if (rule->cond) {
323 ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_RES | partial);
324 if (ret == ACL_TEST_MISS) {
325 /* just set the analyser timeout once at the beginning of the response */
326 if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
327 rep->analyse_exp = tick_add(now_ms, s->be->tcp_rep.inspect_delay);
328 return 0;
329 }
330
331 ret = acl_pass(ret);
332 if (rule->cond->pol == ACL_COND_UNLESS)
333 ret = !ret;
334 }
335
336 if (ret) {
337 act_flags |= ACT_FLAG_FIRST;
338resume_execution:
339 /* we have a matching rule. */
340 if (rule->action == ACT_ACTION_ALLOW) {
341 break;
342 }
343 else if (rule->action == ACT_ACTION_DENY) {
344 channel_abort(rep);
345 channel_abort(&s->req);
346 rep->analysers = 0;
347
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200348 HA_ATOMIC_ADD(&s->be->be_counters.denied_resp, 1);
349 HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_resp, 1);
Willy Tarreaua12dde02016-12-22 18:14:41 +0100350 if (sess->listener && sess->listener->counters)
Christopher Faulet8d8aa0d2017-05-30 15:36:50 +0200351 HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
Willy Tarreau39713102016-11-25 15:49:32 +0100352
353 if (!(s->flags & SF_ERR_MASK))
354 s->flags |= SF_ERR_PRXCOND;
355 if (!(s->flags & SF_FINST_MASK))
356 s->flags |= SF_FINST_D;
357 return 0;
358 }
359 else if (rule->action == ACT_TCP_CLOSE) {
360 chn_prod(rep)->flags |= SI_FL_NOLINGER | SI_FL_NOHALF;
361 si_shutr(chn_prod(rep));
362 si_shutw(chn_prod(rep));
363 break;
364 }
365 else {
366 /* Custom keywords. */
367 if (!rule->action_ptr)
368 continue;
369
370 if (partial & SMP_OPT_FINAL)
371 act_flags |= ACT_FLAG_FINAL;
372
373 switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
374 case ACT_RET_ERR:
375 case ACT_RET_CONT:
376 continue;
377 case ACT_RET_STOP:
378 break;
379 case ACT_RET_YIELD:
380 channel_dont_close(rep);
381 s->current_rule = rule;
382 return 0;
383 }
384 break; /* ACT_RET_STOP */
385 }
386 }
387 }
388
389 /* if we get there, it means we have no rule which matches, or
390 * we have an explicit accept, so we apply the default accept.
391 */
392 rep->analysers &= ~an_bit;
393 rep->analyse_exp = TICK_ETERNITY;
394 return 1;
395}
396
397
398/* This function performs the TCP layer4 analysis on the current request. It
399 * returns 0 if a reject rule matches, otherwise 1 if either an accept rule
400 * matches or if no more rule matches. It can only use rules which don't need
401 * any data. This only works on connection-based client-facing stream interfaces.
402 */
403int tcp_exec_l4_rules(struct session *sess)
404{
405 struct act_rule *rule;
406 struct stksess *ts;
407 struct stktable *t = NULL;
408 struct connection *conn = objt_conn(sess->origin);
409 int result = 1;
410 enum acl_test_res ret;
411
412 if (!conn)
413 return result;
414
415 list_for_each_entry(rule, &sess->fe->tcp_req.l4_rules, list) {
416 ret = ACL_TEST_PASS;
417
418 if (rule->cond) {
419 ret = acl_exec_cond(rule->cond, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
420 ret = acl_pass(ret);
421 if (rule->cond->pol == ACL_COND_UNLESS)
422 ret = !ret;
423 }
424
425 if (ret) {
426 /* we have a matching rule. */
427 if (rule->action == ACT_ACTION_ALLOW) {
428 break;
429 }
430 else if (rule->action == ACT_ACTION_DENY) {
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200431 HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_conn, 1);
Willy Tarreaua12dde02016-12-22 18:14:41 +0100432 if (sess->listener && sess->listener->counters)
Christopher Faulet8d8aa0d2017-05-30 15:36:50 +0200433 HA_ATOMIC_ADD(&sess->listener->counters->denied_conn, 1);
Willy Tarreau39713102016-11-25 15:49:32 +0100434
435 result = 0;
436 break;
437 }
438 else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
439 /* Note: only the first valid tracking parameter of each
440 * applies.
441 */
442 struct stktable_key *key;
443
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200444 if (stkctr_entry(&sess->stkctr[trk_idx(rule->action)]))
Willy Tarreau39713102016-11-25 15:49:32 +0100445 continue;
446
447 t = rule->arg.trk_ctr.table.t;
448 key = stktable_fetch_key(t, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
449
450 if (key && (ts = stktable_get_entry(t, key)))
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200451 stream_track_stkctr(&sess->stkctr[trk_idx(rule->action)], t, ts);
Willy Tarreau39713102016-11-25 15:49:32 +0100452 }
453 else if (rule->action == ACT_TCP_EXPECT_PX) {
454 conn->flags |= CO_FL_ACCEPT_PROXY;
455 conn_sock_want_recv(conn);
456 }
457 else if (rule->action == ACT_TCP_EXPECT_CIP) {
458 conn->flags |= CO_FL_ACCEPT_CIP;
459 conn_sock_want_recv(conn);
460 }
461 else {
462 /* Custom keywords. */
463 if (!rule->action_ptr)
464 break;
465 switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_FLAG_FINAL | ACT_FLAG_FIRST)) {
466 case ACT_RET_YIELD:
467 /* yield is not allowed at this point. If this return code is
468 * used it is a bug, so I prefer to abort the process.
469 */
470 send_log(sess->fe, LOG_WARNING,
471 "Internal error: yield not allowed with tcp-request connection actions.");
472 case ACT_RET_STOP:
473 break;
474 case ACT_RET_CONT:
475 continue;
476 case ACT_RET_ERR:
477 result = 0;
478 break;
479 }
480 break; /* ACT_RET_STOP */
481 }
482 }
483 }
484 return result;
485}
486
487/* This function performs the TCP layer5 analysis on the current request. It
488 * returns 0 if a reject rule matches, otherwise 1 if either an accept rule
489 * matches or if no more rule matches. It can only use rules which don't need
490 * any data. This only works on session-based client-facing stream interfaces.
491 * An example of valid use case is to track a stick-counter on the source
492 * address extracted from the proxy protocol.
493 */
494int tcp_exec_l5_rules(struct session *sess)
495{
496 struct act_rule *rule;
497 struct stksess *ts;
498 struct stktable *t = NULL;
499 int result = 1;
500 enum acl_test_res ret;
501
502 list_for_each_entry(rule, &sess->fe->tcp_req.l5_rules, list) {
503 ret = ACL_TEST_PASS;
504
505 if (rule->cond) {
506 ret = acl_exec_cond(rule->cond, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
507 ret = acl_pass(ret);
508 if (rule->cond->pol == ACL_COND_UNLESS)
509 ret = !ret;
510 }
511
512 if (ret) {
513 /* we have a matching rule. */
514 if (rule->action == ACT_ACTION_ALLOW) {
515 break;
516 }
517 else if (rule->action == ACT_ACTION_DENY) {
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200518 HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_sess, 1);
Willy Tarreaua12dde02016-12-22 18:14:41 +0100519 if (sess->listener && sess->listener->counters)
Christopher Faulet8d8aa0d2017-05-30 15:36:50 +0200520 HA_ATOMIC_ADD(&sess->listener->counters->denied_sess, 1);
Willy Tarreau39713102016-11-25 15:49:32 +0100521
522 result = 0;
523 break;
524 }
525 else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
526 /* Note: only the first valid tracking parameter of each
527 * applies.
528 */
529 struct stktable_key *key;
530
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200531 if (stkctr_entry(&sess->stkctr[trk_idx(rule->action)]))
Willy Tarreau39713102016-11-25 15:49:32 +0100532 continue;
533
534 t = rule->arg.trk_ctr.table.t;
535 key = stktable_fetch_key(t, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
536
537 if (key && (ts = stktable_get_entry(t, key)))
Christopher Faulet4fce0d82017-09-18 11:57:31 +0200538 stream_track_stkctr(&sess->stkctr[trk_idx(rule->action)], t, ts);
Willy Tarreau39713102016-11-25 15:49:32 +0100539 }
540 else {
541 /* Custom keywords. */
542 if (!rule->action_ptr)
543 break;
544 switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_FLAG_FINAL | ACT_FLAG_FIRST)) {
545 case ACT_RET_YIELD:
546 /* yield is not allowed at this point. If this return code is
547 * used it is a bug, so I prefer to abort the process.
548 */
549 send_log(sess->fe, LOG_WARNING,
550 "Internal error: yield not allowed with tcp-request session actions.");
551 case ACT_RET_STOP:
552 break;
553 case ACT_RET_CONT:
554 continue;
555 case ACT_RET_ERR:
556 result = 0;
557 break;
558 }
559 break; /* ACT_RET_STOP */
560 }
561 }
562 }
563 return result;
564}
565
566/* Parse a tcp-response rule. Return a negative value in case of failure */
567static int tcp_parse_response_rule(char **args, int arg, int section_type,
568 struct proxy *curpx, struct proxy *defpx,
569 struct act_rule *rule, char **err,
570 unsigned int where,
571 const char *file, int line)
572{
573 if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
574 memprintf(err, "%s %s is only allowed in 'backend' sections",
575 args[0], args[1]);
576 return -1;
577 }
578
579 if (strcmp(args[arg], "accept") == 0) {
580 arg++;
581 rule->action = ACT_ACTION_ALLOW;
582 }
583 else if (strcmp(args[arg], "reject") == 0) {
584 arg++;
585 rule->action = ACT_ACTION_DENY;
586 }
587 else if (strcmp(args[arg], "close") == 0) {
588 arg++;
589 rule->action = ACT_TCP_CLOSE;
590 }
591 else {
592 struct action_kw *kw;
593 kw = tcp_res_cont_action(args[arg]);
594 if (kw) {
595 arg++;
596 rule->from = ACT_F_TCP_RES_CNT;
597 rule->kw = kw;
598 if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
599 return -1;
600 } else {
601 action_build_list(&tcp_res_cont_keywords, &trash);
602 memprintf(err,
603 "'%s %s' expects 'accept', 'close', 'reject', %s in %s '%s' (got '%s')",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200604 args[0], args[1], trash.area,
605 proxy_type_str(curpx), curpx->id, args[arg]);
Willy Tarreau39713102016-11-25 15:49:32 +0100606 return -1;
607 }
608 }
609
610 if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
Christopher Faulet1b421ea2017-09-22 14:38:56 +0200611 if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
Willy Tarreau39713102016-11-25 15:49:32 +0100612 memprintf(err,
613 "'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
614 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
615 return -1;
616 }
617 }
618 else if (*args[arg]) {
619 memprintf(err,
620 "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
621 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
622 return -1;
623 }
624 return 0;
625}
626
627
628
629/* Parse a tcp-request rule. Return a negative value in case of failure */
630static int tcp_parse_request_rule(char **args, int arg, int section_type,
631 struct proxy *curpx, struct proxy *defpx,
632 struct act_rule *rule, char **err,
633 unsigned int where, const char *file, int line)
634{
635 if (curpx == defpx) {
636 memprintf(err, "%s %s is not allowed in 'defaults' sections",
637 args[0], args[1]);
638 return -1;
639 }
640
641 if (!strcmp(args[arg], "accept")) {
642 arg++;
643 rule->action = ACT_ACTION_ALLOW;
644 }
645 else if (!strcmp(args[arg], "reject")) {
646 arg++;
647 rule->action = ACT_ACTION_DENY;
648 }
649 else if (strcmp(args[arg], "capture") == 0) {
650 struct sample_expr *expr;
651 struct cap_hdr *hdr;
652 int kw = arg;
653 int len = 0;
654
655 if (!(curpx->cap & PR_CAP_FE)) {
656 memprintf(err,
657 "'%s %s %s' : proxy '%s' has no frontend capability",
658 args[0], args[1], args[kw], curpx->id);
659 return -1;
660 }
661
662 if (!(where & SMP_VAL_FE_REQ_CNT)) {
663 memprintf(err,
664 "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
665 args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
666 return -1;
667 }
668
669 arg++;
670
671 curpx->conf.args.ctx = ARGC_CAP;
672 expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
673 if (!expr) {
674 memprintf(err,
675 "'%s %s %s' : %s",
676 args[0], args[1], args[kw], *err);
677 return -1;
678 }
679
680 if (!(expr->fetch->val & where)) {
681 memprintf(err,
682 "'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
683 args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
684 free(expr);
685 return -1;
686 }
687
688 if (strcmp(args[arg], "len") == 0) {
689 arg++;
690 if (!args[arg]) {
691 memprintf(err,
692 "'%s %s %s' : missing length value",
693 args[0], args[1], args[kw]);
694 free(expr);
695 return -1;
696 }
697 /* we copy the table name for now, it will be resolved later */
698 len = atoi(args[arg]);
699 if (len <= 0) {
700 memprintf(err,
701 "'%s %s %s' : length must be > 0",
702 args[0], args[1], args[kw]);
703 free(expr);
704 return -1;
705 }
706 arg++;
707 }
708
709 if (!len) {
710 memprintf(err,
711 "'%s %s %s' : a positive 'len' argument is mandatory",
712 args[0], args[1], args[kw]);
713 free(expr);
714 return -1;
715 }
716
717 hdr = calloc(1, sizeof(*hdr));
718 hdr->next = curpx->req_cap;
719 hdr->name = NULL; /* not a header capture */
720 hdr->namelen = 0;
721 hdr->len = len;
722 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
723 hdr->index = curpx->nb_req_cap++;
724
725 curpx->req_cap = hdr;
726 curpx->to_log |= LW_REQHDR;
727
728 /* check if we need to allocate an hdr_idx struct for HTTP parsing */
729 curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
730
731 rule->arg.cap.expr = expr;
732 rule->arg.cap.hdr = hdr;
733 rule->action = ACT_TCP_CAPTURE;
734 }
Frédéric Lécaillea41d5312018-01-29 12:05:07 +0100735 else if (strncmp(args[arg], "track-sc", 8) == 0) {
Willy Tarreau39713102016-11-25 15:49:32 +0100736 struct sample_expr *expr;
737 int kw = arg;
Frédéric Lécaillea41d5312018-01-29 12:05:07 +0100738 unsigned int tsc_num;
739 const char *tsc_num_str;
Willy Tarreau39713102016-11-25 15:49:32 +0100740
741 arg++;
742
Frédéric Lécaillea41d5312018-01-29 12:05:07 +0100743 tsc_num_str = &args[kw][8];
744 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1) {
745 memprintf(err, "'%s %s %s' : %s", args[0], args[1], args[kw], *err);
746 return -1;
747 }
748
Willy Tarreau39713102016-11-25 15:49:32 +0100749 curpx->conf.args.ctx = ARGC_TRK;
750 expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
751 if (!expr) {
752 memprintf(err,
753 "'%s %s %s' : %s",
754 args[0], args[1], args[kw], *err);
755 return -1;
756 }
757
758 if (!(expr->fetch->val & where)) {
759 memprintf(err,
760 "'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
761 args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
762 free(expr);
763 return -1;
764 }
765
766 /* check if we need to allocate an hdr_idx struct for HTTP parsing */
767 curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
768
769 if (strcmp(args[arg], "table") == 0) {
770 arg++;
771 if (!args[arg]) {
772 memprintf(err,
773 "'%s %s %s' : missing table name",
774 args[0], args[1], args[kw]);
775 free(expr);
776 return -1;
777 }
778 /* we copy the table name for now, it will be resolved later */
779 rule->arg.trk_ctr.table.n = strdup(args[arg]);
780 arg++;
781 }
782 rule->arg.trk_ctr.expr = expr;
Frédéric Lécaillea41d5312018-01-29 12:05:07 +0100783 rule->action = ACT_ACTION_TRK_SC0 + tsc_num;
Christopher Faulet78880fb2017-09-18 14:43:55 +0200784 rule->check_ptr = check_trk_action;
Willy Tarreau39713102016-11-25 15:49:32 +0100785 }
786 else if (strcmp(args[arg], "expect-proxy") == 0) {
787 if (strcmp(args[arg+1], "layer4") != 0) {
788 memprintf(err,
789 "'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
790 args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
791 return -1;
792 }
793
794 if (!(where & SMP_VAL_FE_CON_ACC)) {
795 memprintf(err,
796 "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
797 args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
798 return -1;
799 }
800
801 arg += 2;
802 rule->action = ACT_TCP_EXPECT_PX;
803 }
804 else if (strcmp(args[arg], "expect-netscaler-cip") == 0) {
805 if (strcmp(args[arg+1], "layer4") != 0) {
806 memprintf(err,
807 "'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
808 args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
809 return -1;
810 }
811
812 if (!(where & SMP_VAL_FE_CON_ACC)) {
813 memprintf(err,
814 "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
815 args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
816 return -1;
817 }
818
819 arg += 2;
820 rule->action = ACT_TCP_EXPECT_CIP;
821 }
822 else {
823 struct action_kw *kw;
824 if (where & SMP_VAL_FE_CON_ACC) {
825 /* L4 */
826 kw = tcp_req_conn_action(args[arg]);
827 rule->kw = kw;
828 rule->from = ACT_F_TCP_REQ_CON;
829 } else if (where & SMP_VAL_FE_SES_ACC) {
830 /* L5 */
831 kw = tcp_req_sess_action(args[arg]);
832 rule->kw = kw;
833 rule->from = ACT_F_TCP_REQ_SES;
834 } else {
835 /* L6 */
836 kw = tcp_req_cont_action(args[arg]);
837 rule->kw = kw;
838 rule->from = ACT_F_TCP_REQ_CNT;
839 }
840 if (kw) {
841 arg++;
842 if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
843 return -1;
844 } else {
845 if (where & SMP_VAL_FE_CON_ACC)
846 action_build_list(&tcp_req_conn_keywords, &trash);
847 else if (where & SMP_VAL_FE_SES_ACC)
848 action_build_list(&tcp_req_sess_keywords, &trash);
849 else
850 action_build_list(&tcp_req_cont_keywords, &trash);
851 memprintf(err,
852 "'%s %s' expects 'accept', 'reject', 'track-sc0' ... 'track-sc%d', %s "
853 "in %s '%s' (got '%s').\n",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200854 args[0], args[1], MAX_SESS_STKCTR-1,
855 trash.area, proxy_type_str(curpx),
Willy Tarreau39713102016-11-25 15:49:32 +0100856 curpx->id, args[arg]);
857 return -1;
858 }
859 }
860
861 if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
Christopher Faulet1b421ea2017-09-22 14:38:56 +0200862 if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
Willy Tarreau39713102016-11-25 15:49:32 +0100863 memprintf(err,
864 "'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
865 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
866 return -1;
867 }
868 }
869 else if (*args[arg]) {
870 memprintf(err,
871 "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
872 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
873 return -1;
874 }
875 return 0;
876}
877
878/* This function should be called to parse a line starting with the "tcp-response"
879 * keyword.
880 */
881static int tcp_parse_tcp_rep(char **args, int section_type, struct proxy *curpx,
882 struct proxy *defpx, const char *file, int line,
883 char **err)
884{
885 const char *ptr = NULL;
886 unsigned int val;
887 int warn = 0;
888 int arg;
889 struct act_rule *rule;
890 unsigned int where;
891 const struct acl *acl;
892 const char *kw;
893
894 if (!*args[1]) {
895 memprintf(err, "missing argument for '%s' in %s '%s'",
896 args[0], proxy_type_str(curpx), curpx->id);
897 return -1;
898 }
899
900 if (strcmp(args[1], "inspect-delay") == 0) {
901 if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
902 memprintf(err, "%s %s is only allowed in 'backend' sections",
903 args[0], args[1]);
904 return -1;
905 }
906
907 if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
908 memprintf(err,
909 "'%s %s' expects a positive delay in milliseconds, in %s '%s'",
910 args[0], args[1], proxy_type_str(curpx), curpx->id);
911 if (ptr)
912 memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
913 return -1;
914 }
915
916 if (curpx->tcp_rep.inspect_delay) {
917 memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
918 args[0], args[1], proxy_type_str(curpx), curpx->id);
919 return 1;
920 }
921 curpx->tcp_rep.inspect_delay = val;
922 return 0;
923 }
924
925 rule = calloc(1, sizeof(*rule));
926 LIST_INIT(&rule->list);
927 arg = 1;
928 where = 0;
929
930 if (strcmp(args[1], "content") == 0) {
931 arg++;
932
933 if (curpx->cap & PR_CAP_FE)
934 where |= SMP_VAL_FE_RES_CNT;
935 if (curpx->cap & PR_CAP_BE)
936 where |= SMP_VAL_BE_RES_CNT;
937
938 if (tcp_parse_response_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
939 goto error;
940
941 acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
942 if (acl) {
943 if (acl->name && *acl->name)
944 memprintf(err,
945 "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
946 acl->name, args[0], args[1], sample_ckp_names(where));
947 else
948 memprintf(err,
949 "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
950 args[0], args[1],
951 LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
952 sample_ckp_names(where));
953
954 warn++;
955 }
956 else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
957 if (acl->name && *acl->name)
958 memprintf(err,
959 "acl '%s' involves keyword '%s' which is incompatible with '%s'",
960 acl->name, kw, sample_ckp_names(where));
961 else
962 memprintf(err,
963 "anonymous acl involves keyword '%s' which is incompatible with '%s'",
964 kw, sample_ckp_names(where));
965 warn++;
966 }
967
968 LIST_ADDQ(&curpx->tcp_rep.inspect_rules, &rule->list);
969 }
970 else {
971 memprintf(err,
972 "'%s' expects 'inspect-delay' or 'content' in %s '%s' (got '%s')",
973 args[0], proxy_type_str(curpx), curpx->id, args[1]);
974 goto error;
975 }
976
977 return warn;
978 error:
979 free(rule);
980 return -1;
981}
982
983
984/* This function should be called to parse a line starting with the "tcp-request"
985 * keyword.
986 */
987static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
988 struct proxy *defpx, const char *file, int line,
989 char **err)
990{
991 const char *ptr = NULL;
992 unsigned int val;
993 int warn = 0;
994 int arg;
995 struct act_rule *rule;
996 unsigned int where;
997 const struct acl *acl;
998 const char *kw;
999
1000 if (!*args[1]) {
1001 if (curpx == defpx)
1002 memprintf(err, "missing argument for '%s' in defaults section", args[0]);
1003 else
1004 memprintf(err, "missing argument for '%s' in %s '%s'",
1005 args[0], proxy_type_str(curpx), curpx->id);
1006 return -1;
1007 }
1008
1009 if (!strcmp(args[1], "inspect-delay")) {
1010 if (curpx == defpx) {
1011 memprintf(err, "%s %s is not allowed in 'defaults' sections",
1012 args[0], args[1]);
1013 return -1;
1014 }
1015
1016 if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
1017 memprintf(err,
1018 "'%s %s' expects a positive delay in milliseconds, in %s '%s'",
1019 args[0], args[1], proxy_type_str(curpx), curpx->id);
1020 if (ptr)
1021 memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
1022 return -1;
1023 }
1024
1025 if (curpx->tcp_req.inspect_delay) {
1026 memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
1027 args[0], args[1], proxy_type_str(curpx), curpx->id);
1028 return 1;
1029 }
1030 curpx->tcp_req.inspect_delay = val;
1031 return 0;
1032 }
1033
1034 rule = calloc(1, sizeof(*rule));
1035 LIST_INIT(&rule->list);
1036 arg = 1;
1037 where = 0;
1038
1039 if (strcmp(args[1], "content") == 0) {
1040 arg++;
1041
1042 if (curpx->cap & PR_CAP_FE)
1043 where |= SMP_VAL_FE_REQ_CNT;
1044 if (curpx->cap & PR_CAP_BE)
1045 where |= SMP_VAL_BE_REQ_CNT;
1046
1047 if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1048 goto error;
1049
1050 acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1051 if (acl) {
1052 if (acl->name && *acl->name)
1053 memprintf(err,
1054 "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1055 acl->name, args[0], args[1], sample_ckp_names(where));
1056 else
1057 memprintf(err,
1058 "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1059 args[0], args[1],
1060 LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1061 sample_ckp_names(where));
1062
1063 warn++;
1064 }
1065 else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1066 if (acl->name && *acl->name)
1067 memprintf(err,
1068 "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1069 acl->name, kw, sample_ckp_names(where));
1070 else
1071 memprintf(err,
1072 "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1073 kw, sample_ckp_names(where));
1074 warn++;
1075 }
1076
1077 /* the following function directly emits the warning */
1078 warnif_misplaced_tcp_cont(curpx, file, line, args[0]);
1079 LIST_ADDQ(&curpx->tcp_req.inspect_rules, &rule->list);
1080 }
1081 else if (strcmp(args[1], "connection") == 0) {
1082 arg++;
1083
1084 if (!(curpx->cap & PR_CAP_FE)) {
1085 memprintf(err, "%s %s is not allowed because %s %s is not a frontend",
1086 args[0], args[1], proxy_type_str(curpx), curpx->id);
1087 goto error;
1088 }
1089
1090 where |= SMP_VAL_FE_CON_ACC;
1091
1092 if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1093 goto error;
1094
1095 acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1096 if (acl) {
1097 if (acl->name && *acl->name)
1098 memprintf(err,
1099 "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1100 acl->name, args[0], args[1], sample_ckp_names(where));
1101 else
1102 memprintf(err,
1103 "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1104 args[0], args[1],
1105 LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1106 sample_ckp_names(where));
1107
1108 warn++;
1109 }
1110 else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1111 if (acl->name && *acl->name)
1112 memprintf(err,
1113 "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1114 acl->name, kw, sample_ckp_names(where));
1115 else
1116 memprintf(err,
1117 "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1118 kw, sample_ckp_names(where));
1119 warn++;
1120 }
1121
1122 /* the following function directly emits the warning */
1123 warnif_misplaced_tcp_conn(curpx, file, line, args[0]);
1124 LIST_ADDQ(&curpx->tcp_req.l4_rules, &rule->list);
1125 }
1126 else if (strcmp(args[1], "session") == 0) {
1127 arg++;
1128
1129 if (!(curpx->cap & PR_CAP_FE)) {
1130 memprintf(err, "%s %s is not allowed because %s %s is not a frontend",
1131 args[0], args[1], proxy_type_str(curpx), curpx->id);
1132 goto error;
1133 }
1134
1135 where |= SMP_VAL_FE_SES_ACC;
1136
1137 if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1138 goto error;
1139
1140 acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1141 if (acl) {
1142 if (acl->name && *acl->name)
1143 memprintf(err,
1144 "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1145 acl->name, args[0], args[1], sample_ckp_names(where));
1146 else
1147 memprintf(err,
1148 "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1149 args[0], args[1],
1150 LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1151 sample_ckp_names(where));
1152 warn++;
1153 }
1154 else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1155 if (acl->name && *acl->name)
1156 memprintf(err,
1157 "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1158 acl->name, kw, sample_ckp_names(where));
1159 else
1160 memprintf(err,
1161 "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1162 kw, sample_ckp_names(where));
1163 warn++;
1164 }
1165
1166 /* the following function directly emits the warning */
1167 warnif_misplaced_tcp_sess(curpx, file, line, args[0]);
1168 LIST_ADDQ(&curpx->tcp_req.l5_rules, &rule->list);
1169 }
1170 else {
1171 if (curpx == defpx)
1172 memprintf(err,
1173 "'%s' expects 'inspect-delay', 'connection', or 'content' in defaults section (got '%s')",
1174 args[0], args[1]);
1175 else
1176 memprintf(err,
1177 "'%s' expects 'inspect-delay', 'connection', or 'content' in %s '%s' (got '%s')",
1178 args[0], proxy_type_str(curpx), curpx->id, args[1]);
1179 goto error;
1180 }
1181
1182 return warn;
1183 error:
1184 free(rule);
1185 return -1;
1186}
1187
1188static struct cfg_kw_list cfg_kws = {ILH, {
1189 { CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
1190 { CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
1191 { 0, NULL, NULL },
1192}};
1193
1194
1195__attribute__((constructor))
1196static void __tcp_protocol_init(void)
1197{
1198 cfg_register_keywords(&cfg_kws);
1199}
1200
1201/*
1202 * Local variables:
1203 * c-indent-level: 8
1204 * c-basic-offset: 8
1205 * End:
1206 */