blob: b684bff4303890ddf006bff218a11a7dd7b6ed03 [file] [log] [blame]
Willy Tarreauaeae66c2020-08-28 11:03:28 +02001/*
2 * AF_INET/AF_INET6 SOCK_STREAM protocol layer (tcp)
3 *
4 * Copyright 2000-2013 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
13#include <ctype.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <time.h>
20
21#include <sys/param.h>
22#include <sys/socket.h>
23#include <sys/types.h>
24
25#include <netinet/tcp.h>
26#include <netinet/in.h>
27
28#include <haproxy/action-t.h>
29#include <haproxy/api.h>
30#include <haproxy/arg.h>
31#include <haproxy/channel.h>
32#include <haproxy/connection.h>
33#include <haproxy/global.h>
34#include <haproxy/http_rules.h>
35#include <haproxy/proto_tcp.h>
36#include <haproxy/proxy-t.h>
37#include <haproxy/sample.h>
38#include <haproxy/stream-t.h>
39#include <haproxy/tcp_rules.h>
40#include <haproxy/tools.h>
41
42/*
43 * Execute the "set-src" action. May be called from {tcp,http}request.
44 * It only changes the address and tries to preserve the original port. If the
45 * previous family was neither AF_INET nor AF_INET6, the port is set to zero.
46 */
47static enum act_return tcp_action_req_set_src(struct act_rule *rule, struct proxy *px,
48 struct session *sess, struct stream *s, int flags)
49{
50 struct connection *cli_conn;
51
52 if ((cli_conn = objt_conn(sess->origin)) && conn_get_src(cli_conn)) {
53 struct sample *smp;
54
55 smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr, SMP_T_ADDR);
56 if (smp) {
57 int port = get_net_port(cli_conn->src);
58
59 if (smp->data.type == SMP_T_IPV4) {
60 ((struct sockaddr_in *)cli_conn->src)->sin_family = AF_INET;
61 ((struct sockaddr_in *)cli_conn->src)->sin_addr.s_addr = smp->data.u.ipv4.s_addr;
62 ((struct sockaddr_in *)cli_conn->src)->sin_port = port;
63 } else if (smp->data.type == SMP_T_IPV6) {
64 ((struct sockaddr_in6 *)cli_conn->src)->sin6_family = AF_INET6;
65 memcpy(&((struct sockaddr_in6 *)cli_conn->src)->sin6_addr, &smp->data.u.ipv6, sizeof(struct in6_addr));
66 ((struct sockaddr_in6 *)cli_conn->src)->sin6_port = port;
67 }
68 }
69 cli_conn->flags |= CO_FL_ADDR_FROM_SET;
70 }
71 return ACT_RET_CONT;
72}
73
74/*
75 * Execute the "set-dst" action. May be called from {tcp,http}request.
76 * It only changes the address and tries to preserve the original port. If the
77 * previous family was neither AF_INET nor AF_INET6, the port is set to zero.
78 */
79static enum act_return tcp_action_req_set_dst(struct act_rule *rule, struct proxy *px,
80 struct session *sess, struct stream *s, int flags)
81{
82 struct connection *cli_conn;
83
84 if ((cli_conn = objt_conn(sess->origin)) && conn_get_dst(cli_conn)) {
85 struct sample *smp;
86
87 smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr, SMP_T_ADDR);
88 if (smp) {
89 int port = get_net_port(cli_conn->dst);
90
91 if (smp->data.type == SMP_T_IPV4) {
92 ((struct sockaddr_in *)cli_conn->dst)->sin_family = AF_INET;
93 ((struct sockaddr_in *)cli_conn->dst)->sin_addr.s_addr = smp->data.u.ipv4.s_addr;
Christopher Faulete01ca0f2021-03-01 11:21:14 +010094 ((struct sockaddr_in *)cli_conn->dst)->sin_port = port;
Willy Tarreauaeae66c2020-08-28 11:03:28 +020095 } else if (smp->data.type == SMP_T_IPV6) {
96 ((struct sockaddr_in6 *)cli_conn->dst)->sin6_family = AF_INET6;
97 memcpy(&((struct sockaddr_in6 *)cli_conn->dst)->sin6_addr, &smp->data.u.ipv6, sizeof(struct in6_addr));
98 ((struct sockaddr_in6 *)cli_conn->dst)->sin6_port = port;
99 }
100 cli_conn->flags |= CO_FL_ADDR_TO_SET;
101 }
102 }
103 return ACT_RET_CONT;
104}
105
106/*
107 * Execute the "set-src-port" action. May be called from {tcp,http}request.
108 * We must test the sin_family before setting the port. If the address family
109 * is neither AF_INET nor AF_INET6, the address is forced to AF_INET "0.0.0.0"
110 * and the port is assigned.
111 */
112static enum act_return tcp_action_req_set_src_port(struct act_rule *rule, struct proxy *px,
113 struct session *sess, struct stream *s, int flags)
114{
115 struct connection *cli_conn;
116
117 if ((cli_conn = objt_conn(sess->origin)) && conn_get_src(cli_conn)) {
118 struct sample *smp;
119
120 smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr, SMP_T_SINT);
121 if (smp) {
122 if (cli_conn->src->ss_family == AF_INET6) {
123 ((struct sockaddr_in6 *)cli_conn->src)->sin6_port = htons(smp->data.u.sint);
124 } else {
125 if (cli_conn->src->ss_family != AF_INET) {
126 cli_conn->src->ss_family = AF_INET;
127 ((struct sockaddr_in *)cli_conn->src)->sin_addr.s_addr = 0;
128 }
129 ((struct sockaddr_in *)cli_conn->src)->sin_port = htons(smp->data.u.sint);
130 }
131 }
132 }
133 return ACT_RET_CONT;
134}
135
136/*
137 * Execute the "set-dst-port" action. May be called from {tcp,http}request.
138 * We must test the sin_family before setting the port. If the address family
139 * is neither AF_INET nor AF_INET6, the address is forced to AF_INET "0.0.0.0"
140 * and the port is assigned.
141 */
142static enum act_return tcp_action_req_set_dst_port(struct act_rule *rule, struct proxy *px,
143 struct session *sess, struct stream *s, int flags)
144{
145 struct connection *cli_conn;
146
147 if ((cli_conn = objt_conn(sess->origin)) && conn_get_dst(cli_conn)) {
148 struct sample *smp;
149
150 smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr, SMP_T_SINT);
151 if (smp) {
152 if (cli_conn->dst->ss_family == AF_INET6) {
153 ((struct sockaddr_in6 *)cli_conn->dst)->sin6_port = htons(smp->data.u.sint);
154 } else {
155 if (cli_conn->dst->ss_family != AF_INET) {
156 cli_conn->dst->ss_family = AF_INET;
157 ((struct sockaddr_in *)cli_conn->dst)->sin_addr.s_addr = 0;
158 }
159 ((struct sockaddr_in *)cli_conn->dst)->sin_port = htons(smp->data.u.sint);
160 }
161 }
162 }
163 return ACT_RET_CONT;
164}
165
166/* Executes the "silent-drop" action. May be called from {tcp,http}{request,response} */
167static enum act_return tcp_exec_action_silent_drop(struct act_rule *rule, struct proxy *px,
168 struct session *sess, struct stream *strm, int flags)
169{
170 struct connection *conn = objt_conn(sess->origin);
171
172 if (!conn)
173 goto out;
174
175 if (!conn_ctrl_ready(conn))
176 goto out;
177
178#ifdef TCP_QUICKACK
179 /* drain is needed only to send the quick ACK */
Willy Tarreau2ded48d2020-12-11 16:20:34 +0100180 conn_ctrl_drain(conn);
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200181
182 /* re-enable quickack if it was disabled to ack all data and avoid
183 * retransmits from the client that might trigger a real reset.
184 */
Willy Tarreau4bfc6632021-03-31 08:45:47 +0200185 setsockopt(conn->handle.fd, IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one));
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200186#endif
187 /* lingering must absolutely be disabled so that we don't send a
188 * shutdown(), this is critical to the TCP_REPAIR trick. When no stream
189 * is present, returning with ERR will cause lingering to be disabled.
190 */
191 if (strm)
192 strm->si[0].flags |= SI_FL_NOLINGER;
193
194 /* We're on the client-facing side, we must force to disable lingering to
195 * ensure we will use an RST exclusively and kill any pending data.
196 */
Willy Tarreaub41a6e92021-04-06 17:49:19 +0200197 HA_ATOMIC_OR(&fdtab[conn->handle.fd].state, FD_LINGER_RISK);
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200198
199#ifdef TCP_REPAIR
Willy Tarreau4bfc6632021-03-31 08:45:47 +0200200 if (setsockopt(conn->handle.fd, IPPROTO_TCP, TCP_REPAIR, &one, sizeof(one)) == 0) {
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200201 /* socket will be quiet now */
202 goto out;
203 }
204#endif
205 /* either TCP_REPAIR is not defined or it failed (eg: permissions).
206 * Let's fall back on the TTL trick, though it only works for routed
207 * network and has no effect on local net.
208 */
209#ifdef IP_TTL
Willy Tarreauab79ee82021-03-30 17:23:50 +0200210 if (conn->src && conn->src->ss_family == AF_INET)
Willy Tarreau4bfc6632021-03-31 08:45:47 +0200211 setsockopt(conn->handle.fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
Willy Tarreauab79ee82021-03-30 17:23:50 +0200212#endif
213#ifdef IPV6_UNICAST_HOPS
Willy Tarreauda231952021-03-31 08:29:27 +0200214 if (conn->src && conn->src->ss_family == AF_INET6)
215 setsockopt(conn->handle.fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one));
216#endif
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200217 out:
218 /* kill the stream if any */
219 if (strm) {
220 channel_abort(&strm->req);
221 channel_abort(&strm->res);
222 strm->req.analysers &= AN_REQ_FLT_END;
223 strm->res.analysers &= AN_RES_FLT_END;
224 if (strm->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200225 _HA_ATOMIC_INC(&strm->be->be_counters.denied_req);
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200226 if (!(strm->flags & SF_ERR_MASK))
227 strm->flags |= SF_ERR_PRXCOND;
228 if (!(strm->flags & SF_FINST_MASK))
229 strm->flags |= SF_FINST_R;
230 }
231
Willy Tarreau4781b152021-04-06 13:53:36 +0200232 _HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
William Lallemand36119de2021-03-08 15:26:48 +0100233 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200234 _HA_ATOMIC_INC(&sess->listener->counters->denied_req);
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200235
236 return ACT_RET_ABRT;
237}
238
Christopher Faulet469c06c2021-06-25 15:11:35 +0200239
Willy Tarreau5bbfff12021-06-28 07:12:22 +0200240#if defined(SO_MARK) || defined(SO_USER_COOKIE)
Christopher Faulet469c06c2021-06-25 15:11:35 +0200241static enum act_return tcp_action_set_mark(struct act_rule *rule, struct proxy *px,
242 struct session *sess, struct stream *s, int flags)
243{
244 conn_set_mark(objt_conn(sess->origin), (uintptr_t)rule->arg.act.p[0]);
245 return ACT_RET_CONT;
246}
Willy Tarreau5bbfff12021-06-28 07:12:22 +0200247#endif
Christopher Faulet469c06c2021-06-25 15:11:35 +0200248
Willy Tarreau5bbfff12021-06-28 07:12:22 +0200249#ifdef IP_TOS
Christopher Faulet469c06c2021-06-25 15:11:35 +0200250static enum act_return tcp_action_set_tos(struct act_rule *rule, struct proxy *px,
251 struct session *sess, struct stream *s, int flags)
252{
253 conn_set_tos(objt_conn(sess->origin), (uintptr_t)rule->arg.act.p[0]);
254 return ACT_RET_CONT;
255}
Willy Tarreau5bbfff12021-06-28 07:12:22 +0200256#endif
Christopher Faulet469c06c2021-06-25 15:11:35 +0200257
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200258/* parse "set-{src,dst}[-port]" action */
259static enum act_parse_ret tcp_parse_set_src_dst(const char **args, int *orig_arg, struct proxy *px,
260 struct act_rule *rule, char **err)
261{
262 int cur_arg;
263 struct sample_expr *expr;
264 unsigned int where;
265
266 cur_arg = *orig_arg;
267 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
268 if (!expr)
269 return ACT_RET_PRS_ERR;
270
271 where = 0;
272 if (px->cap & PR_CAP_FE)
273 where |= SMP_VAL_FE_HRQ_HDR;
274 if (px->cap & PR_CAP_BE)
275 where |= SMP_VAL_BE_HRQ_HDR;
276
277 if (!(expr->fetch->val & where)) {
278 memprintf(err,
279 "fetch method '%s' extracts information from '%s', none of which is available here",
280 args[cur_arg-1], sample_src_names(expr->fetch->use));
281 free(expr);
282 return ACT_RET_PRS_ERR;
283 }
284 rule->arg.expr = expr;
285 rule->action = ACT_CUSTOM;
286
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100287 if (strcmp(args[*orig_arg - 1], "set-src") == 0) {
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200288 rule->action_ptr = tcp_action_req_set_src;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100289 } else if (strcmp(args[*orig_arg - 1], "set-src-port") == 0) {
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200290 rule->action_ptr = tcp_action_req_set_src_port;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100291 } else if (strcmp(args[*orig_arg - 1], "set-dst") == 0) {
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200292 rule->action_ptr = tcp_action_req_set_dst;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100293 } else if (strcmp(args[*orig_arg - 1], "set-dst-port") == 0) {
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200294 rule->action_ptr = tcp_action_req_set_dst_port;
295 } else {
296 return ACT_RET_PRS_ERR;
297 }
298
299 (*orig_arg)++;
300
Christopher Faulet469c06c2021-06-25 15:11:35 +0200301 return ACT_RET_PRS_OK;
302}
303
304
305/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
306 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
307 */
308static enum act_parse_ret tcp_parse_set_mark(const char **args, int *cur_arg, struct proxy *px,
309 struct act_rule *rule, char **err)
310{
David Carlierf7f53af2021-06-26 12:04:36 +0100311#if defined(SO_MARK) || defined(SO_USER_COOKIE)
Christopher Faulet469c06c2021-06-25 15:11:35 +0200312 char *endp;
313 unsigned int mark;
314
315 if (!*args[*cur_arg]) {
316 memprintf(err, "expects exactly 1 argument (integer/hex value)");
317 return ACT_RET_PRS_ERR;
318 }
319 mark = strtoul(args[*cur_arg], &endp, 0);
320 if (endp && *endp != '\0') {
321 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
322 return ACT_RET_PRS_ERR;
323 }
324
325 (*cur_arg)++;
326
327 /* Register processing function. */
328 rule->action_ptr = tcp_action_set_mark;
329 rule->action = ACT_CUSTOM;
330 rule->arg.act.p[0] = (void *)(uintptr_t)mark;
331 global.last_checks |= LSTCHK_NETADM;
332 return ACT_RET_PRS_OK;
333#else
David Carlierf7f53af2021-06-26 12:04:36 +0100334 memprintf(err, "not supported on this platform (SO_MARK|SO_USER_COOKIE undefined)");
Christopher Faulet469c06c2021-06-25 15:11:35 +0200335 return ACT_RET_PRS_ERR;
336#endif
337}
338
339
340/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
341 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
342 */
343static enum act_parse_ret tcp_parse_set_tos(const char **args, int *cur_arg, struct proxy *px,
344 struct act_rule *rule, char **err)
345{
346#ifdef IP_TOS
347 char *endp;
348 int tos;
349
350 if (!*args[*cur_arg]) {
351 memprintf(err, "expects exactly 1 argument (integer/hex value)");
352 return ACT_RET_PRS_ERR;
353 }
354 tos = strtol(args[*cur_arg], &endp, 0);
355 if (endp && *endp != '\0') {
356 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
357 return ACT_RET_PRS_ERR;
358 }
359
360 (*cur_arg)++;
361
362 /* Register processing function. */
363 rule->action_ptr = tcp_action_set_tos;
364 rule->action = ACT_CUSTOM;
365 rule->arg.act.p[0] = (void *)(uintptr_t)tos;
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200366 return ACT_RET_PRS_OK;
Christopher Faulet469c06c2021-06-25 15:11:35 +0200367#else
368 memprintf(err, "not supported on this platform (IP_TOS undefined)");
369 return ACT_RET_PRS_ERR;
370#endif
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200371}
372
373
374/* Parse a "silent-drop" action. It takes no argument. It returns ACT_RET_PRS_OK on
375 * success, ACT_RET_PRS_ERR on error.
376 */
377static enum act_parse_ret tcp_parse_silent_drop(const char **args, int *orig_arg, struct proxy *px,
378 struct act_rule *rule, char **err)
379{
380 rule->action = ACT_CUSTOM;
381 rule->action_ptr = tcp_exec_action_silent_drop;
382 return ACT_RET_PRS_OK;
383}
384
385
386static struct action_kw_list tcp_req_conn_actions = {ILH, {
Christopher Fauletee9c98d2021-06-25 15:18:33 +0200387 { "set-dst" , tcp_parse_set_src_dst },
388 { "set-dst-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200389 { "set-mark", tcp_parse_set_mark },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200390 { "set-src", tcp_parse_set_src_dst },
391 { "set-src-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200392 { "set-tos", tcp_parse_set_tos },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200393 { "silent-drop", tcp_parse_silent_drop },
394 { /* END */ }
395}};
396
397INITCALL1(STG_REGISTER, tcp_req_conn_keywords_register, &tcp_req_conn_actions);
398
399static struct action_kw_list tcp_req_sess_actions = {ILH, {
Christopher Fauletee9c98d2021-06-25 15:18:33 +0200400 { "set-dst" , tcp_parse_set_src_dst },
401 { "set-dst-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200402 { "set-mark", tcp_parse_set_mark },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200403 { "set-src", tcp_parse_set_src_dst },
404 { "set-src-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200405 { "set-tos", tcp_parse_set_tos },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200406 { "silent-drop", tcp_parse_silent_drop },
407 { /* END */ }
408}};
409
410INITCALL1(STG_REGISTER, tcp_req_sess_keywords_register, &tcp_req_sess_actions);
411
412static struct action_kw_list tcp_req_cont_actions = {ILH, {
Christopher Fauletee9c98d2021-06-25 15:18:33 +0200413 { "set-dst" , tcp_parse_set_src_dst },
414 { "set-dst-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200415 { "set-mark", tcp_parse_set_mark },
Christopher Faulet19bbbe02021-06-23 12:07:21 +0200416 { "set-src", tcp_parse_set_src_dst },
417 { "set-src-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200418 { "set-tos", tcp_parse_set_tos },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200419 { "silent-drop", tcp_parse_silent_drop },
420 { /* END */ }
421}};
422
423INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_cont_actions);
424
425static struct action_kw_list tcp_res_cont_actions = {ILH, {
Christopher Faulet469c06c2021-06-25 15:11:35 +0200426 { "set-mark", tcp_parse_set_mark },
427 { "set-tos", tcp_parse_set_tos },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200428 { "silent-drop", tcp_parse_silent_drop },
429 { /* END */ }
430}};
431
432INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_cont_actions);
433
434static struct action_kw_list http_req_actions = {ILH, {
Christopher Fauletee9c98d2021-06-25 15:18:33 +0200435 { "set-dst", tcp_parse_set_src_dst },
436 { "set-dst-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200437 { "set-mark", tcp_parse_set_mark },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200438 { "set-src", tcp_parse_set_src_dst },
439 { "set-src-port", tcp_parse_set_src_dst },
Christopher Faulet469c06c2021-06-25 15:11:35 +0200440 { "set-tos", tcp_parse_set_tos },
Christopher Fauletee9c98d2021-06-25 15:18:33 +0200441 { "silent-drop", tcp_parse_silent_drop },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200442 { /* END */ }
443}};
444
445INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
446
447static struct action_kw_list http_res_actions = {ILH, {
Christopher Faulet469c06c2021-06-25 15:11:35 +0200448 { "set-mark", tcp_parse_set_mark },
449 { "set-tos", tcp_parse_set_tos },
Willy Tarreauaeae66c2020-08-28 11:03:28 +0200450 { "silent-drop", tcp_parse_silent_drop },
451 { /* END */ }
452}};
453
454INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
455
456
457/*
458 * Local variables:
459 * c-indent-level: 8
460 * c-basic-offset: 8
461 * End:
462 */