forked from haproxy/haproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp_ana.c
5162 lines (4498 loc) · 163 KB
/
http_ana.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* HTTP protocol analyzer
*
* Copyright (C) 2018 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <haproxy/acl.h>
#include <haproxy/action-t.h>
#include <haproxy/api.h>
#include <haproxy/applet.h>
#include <haproxy/backend.h>
#include <haproxy/base64.h>
#include <haproxy/capture-t.h>
#include <haproxy/cfgparse.h>
#include <haproxy/channel.h>
#include <haproxy/check.h>
#include <haproxy/connection.h>
#include <haproxy/errors.h>
#include <haproxy/filters.h>
#include <haproxy/http.h>
#include <haproxy/http_ana.h>
#include <haproxy/http_htx.h>
#include <haproxy/http_ext.h>
#include <haproxy/htx.h>
#include <haproxy/log.h>
#include <haproxy/net_helper.h>
#include <haproxy/proxy.h>
#include <haproxy/regex.h>
#include <haproxy/sc_strm.h>
#include <haproxy/server-t.h>
#include <haproxy/stats.h>
#include <haproxy/stats-html.h>
#include <haproxy/stconn.h>
#include <haproxy/stream.h>
#include <haproxy/trace.h>
#include <haproxy/uri_auth-t.h>
#include <haproxy/vars.h>
#define TRACE_SOURCE &trace_strm
extern const char *stat_status_codes[];
struct pool_head *pool_head_requri __read_mostly = NULL;
struct pool_head *pool_head_capture __read_mostly = NULL;
static void http_end_request(struct stream *s);
static void http_end_response(struct stream *s);
static void http_capture_headers(struct htx *htx, char **cap, struct cap_hdr *cap_hdr);
static int http_del_hdr_value(char *start, char *end, char **from, char *next);
static size_t http_fmt_req_line(const struct htx_sl *sl, char *str, size_t len);
static void http_debug_stline(const char *dir, struct stream *s, const struct htx_sl *sl);
static void http_debug_hdr(const char *dir, struct stream *s, const struct ist n, const struct ist v);
static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct list *def_rules, struct list *rules, struct stream *s);
static enum rule_result http_res_get_intercept_rule(struct proxy *px, struct list *def_rules, struct list *rules, struct stream *s, uint8_t final);
static enum rule_result http_req_restrict_header_names(struct stream *s, struct htx *htx, struct proxy *px);
static void http_manage_client_side_cookies(struct stream *s, struct channel *req);
static void http_manage_server_side_cookies(struct stream *s, struct channel *res);
static int http_stats_check_uri(struct stream *s, struct http_txn *txn, struct proxy *px);
static int http_handle_stats(struct stream *s, struct channel *req, struct proxy *px);
static int http_handle_expect_hdr(struct stream *s, struct htx *htx, struct http_msg *msg);
static int http_reply_100_continue(struct stream *s);
/* This stream analyser waits for a complete HTTP request. It returns 1 if the
* processing can continue on next analysers, or zero if it either needs more
* data or wants to immediately abort the request (eg: timeout, error, ...). It
* is tied to AN_REQ_WAIT_HTTP and may may remove itself from s->req.analysers
* when it has nothing left to do, and may remove any analyser when it wants to
* abort.
*/
int http_wait_for_request(struct stream *s, struct channel *req, int an_bit)
{
/*
* We will analyze a complete HTTP request to check the its syntax.
*
* Once the start line and all headers are received, we may perform a
* capture of the error (if any), and we will set a few fields. We also
* check for monitor-uri, logging and finally headers capture.
*/
struct session *sess = s->sess;
struct http_txn *txn = s->txn;
struct http_msg *msg = &txn->req;
struct htx *htx;
struct htx_sl *sl;
char http_ver;
int len;
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, msg);
if (unlikely(!IS_HTX_STRM(s))) {
/* It is only possible when a TCP stream is upgrade to HTTP.
* There is a transition period during which there is no
* data. The stream is still in raw mode and SF_IGNORE flag is
* still set. When this happens, the new mux is responsible to
* handle all errors. Thus we may leave immediately.
*/
BUG_ON(!(s->flags & SF_IGNORE) || !c_empty(&s->req));
/* Don't connect for now */
channel_dont_connect(req);
/* An abort at this stage means we are performing a "destructive"
* HTTP upgrade (TCP>H2). In this case, we can leave.
*/
if (s->scf->flags & (SC_FL_ABRT_DONE|SC_FL_EOS)) {
s->logs.logwait = 0;
s->logs.level = 0;
stream_abort(s);
req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY;
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA, s);
return 1;
}
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA, s);
return 0;
}
htx = htxbuf(&req->buf);
sl = http_get_stline(htx);
len = HTX_SL_REQ_VLEN(sl);
if (len < 6) {
http_ver = 0;
}
else {
char *ptr;
ptr = HTX_SL_REQ_VPTR(sl);
http_ver = ptr[5] - '0';
}
/* Parsing errors are caught here */
if (htx->flags & (HTX_FL_PARSING_ERROR|HTX_FL_PROCESSING_ERROR)) {
stream_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(sess->listener, sess->fe, http_ver);
if (htx->flags & HTX_FL_PARSING_ERROR) {
stream_inc_http_err_ctr(s);
goto return_bad_req;
}
else
goto return_int_err;
}
/* we're speaking HTTP here, so let's speak HTTP to the client */
s->srv_error = http_return_srv_error;
msg->msg_state = HTTP_MSG_BODY;
stream_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(sess->listener, sess->fe, http_ver); /* one more valid request for this FE */
/* kill the pending keep-alive timeout */
req->analyse_exp = TICK_ETERNITY;
BUG_ON(htx_get_first_type(htx) != HTX_BLK_REQ_SL);
/* 0: we might have to print this header in debug mode */
if (unlikely((global.mode & MODE_DEBUG) &&
(!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) {
int32_t pos;
http_debug_stline("clireq", s, sl);
for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
struct htx_blk *blk = htx_get_blk(htx, pos);
enum htx_blk_type type = htx_get_blk_type(blk);
if (type == HTX_BLK_EOH)
break;
if (type != HTX_BLK_HDR)
continue;
http_debug_hdr("clihdr", s,
htx_get_blk_name(htx, blk),
htx_get_blk_value(htx, blk));
}
}
/*
* 1: identify the method and the version. Also set HTTP flags
*/
txn->meth = sl->info.req.meth;
if (sl->flags & HTX_SL_F_VER_11)
msg->flags |= HTTP_MSGF_VER_11;
msg->flags |= HTTP_MSGF_XFER_LEN;
if (sl->flags & HTX_SL_F_CLEN)
msg->flags |= HTTP_MSGF_CNT_LEN;
else if (sl->flags & HTX_SL_F_CHNK)
msg->flags |= HTTP_MSGF_TE_CHNK;
if (sl->flags & HTX_SL_F_BODYLESS)
msg->flags |= HTTP_MSGF_BODYLESS;
if (sl->flags & HTX_SL_F_CONN_UPG)
msg->flags |= HTTP_MSGF_CONN_UPG;
/* we can make use of server redirect on GET and HEAD */
if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
s->flags |= SF_REDIRECTABLE;
else if (txn->meth == HTTP_METH_OTHER && isteqi(htx_sl_req_meth(sl), ist("PRI"))) {
/* PRI is reserved for the HTTP/2 preface */
goto return_bad_req;
}
/*
* 2: check if the URI matches the monitor_uri. We have to do this for
* every request which gets in, because the monitor-uri is defined by
* the frontend. If the monitor-uri starts with a '/', the matching is
* done against the request's path. Otherwise, the request's uri is
* used. It is a workaround to let HTTP/2 health-checks work as
* expected.
*/
if (unlikely(isttest(sess->fe->monitor_uri))) {
const struct ist monitor_uri = sess->fe->monitor_uri;
struct http_uri_parser parser = http_uri_parser_init(htx_sl_req_uri(sl));
if ((istptr(monitor_uri)[0] == '/' &&
isteq(http_parse_path(&parser), monitor_uri)) ||
isteq(htx_sl_req_uri(sl), monitor_uri)) {
/*
* We have found the monitor URI
*/
struct acl_cond *cond;
s->flags |= SF_MONITOR;
_HA_ATOMIC_INC(&sess->fe->fe_counters.intercepted_req);
/* Check if we want to fail this monitor request or not */
list_for_each_entry(cond, &sess->fe->mon_fail_cond, list) {
int ret = acl_exec_cond(cond, sess->fe, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
ret = acl_pass(ret);
if (cond->pol == ACL_COND_UNLESS)
ret = !ret;
if (ret) {
/* we fail this request, let's return 503 service unavail */
txn->status = 503;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL; /* we don't want a real error here */
goto return_prx_cond;
}
}
/* nothing to fail, let's reply normally */
txn->status = 200;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL; /* we don't want a real error here */
goto return_prx_cond;
}
}
/*
* 3: Maybe we have to copy the original REQURI for the logs ?
* Note: we cannot log anymore if the request has been
* classified as invalid.
*/
if (unlikely(s->logs.logwait & LW_REQ)) {
/* we have a complete HTTP request that we must log */
if ((txn->uri = pool_alloc(pool_head_requri)) != NULL) {
size_t len;
len = http_fmt_req_line(sl, txn->uri, global.tune.requri_len - 1);
txn->uri[len] = 0;
if (!(s->logs.logwait &= ~(LW_REQ|LW_INIT)))
s->do_log(s);
} else {
ha_alert("HTTP logging : out of memory.\n");
}
}
/* if the frontend has "option http-use-proxy-header", we'll check if
* we have what looks like a proxied connection instead of a connection,
* and in this case set the TX_USE_PX_CONN flag to use Proxy-connection.
* Note that this is *not* RFC-compliant, however browsers and proxies
* happen to do that despite being non-standard :-(
* We consider that a request not beginning with either '/' or '*' is
* a proxied connection, which covers both "scheme://location" and
* CONNECT ip:port.
*/
if ((sess->fe->options2 & PR_O2_USE_PXHDR) &&
*HTX_SL_REQ_UPTR(sl) != '/' && *HTX_SL_REQ_UPTR(sl) != '*')
txn->flags |= TX_USE_PX_CONN;
/* 5: we may need to capture headers */
if (unlikely((s->logs.logwait & LW_REQHDR) && s->req_cap))
http_capture_headers(htx, s->req_cap, sess->fe->req_cap);
/* we may have to wait for the request's body */
if (s->be->options & PR_O_WREQ_BODY)
req->analysers |= AN_REQ_HTTP_BODY;
/*
* RFC7234#4:
* A cache MUST write through requests with methods
* that are unsafe (Section 4.2.1 of [RFC7231]) to
* the origin server; i.e., a cache is not allowed
* to generate a reply to such a request before
* having forwarded the request and having received
* a corresponding response.
*
* RFC7231#4.2.1:
* Of the request methods defined by this
* specification, the GET, HEAD, OPTIONS, and TRACE
* methods are defined to be safe.
*/
if (likely(txn->meth == HTTP_METH_GET ||
txn->meth == HTTP_METH_HEAD ||
txn->meth == HTTP_METH_OPTIONS ||
txn->meth == HTTP_METH_TRACE))
txn->flags |= TX_CACHEABLE | TX_CACHE_COOK;
/* end of job, return OK */
req->analysers &= ~an_bit;
req->analyse_exp = TICK_ETERNITY;
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 1;
return_int_err:
txn->status = 500;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_INTERNAL;
_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->internal_errors);
goto return_prx_cond;
return_bad_req:
txn->status = 400;
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->failed_req);
/* fall through */
return_prx_cond:
http_set_term_flags(s);
http_reply_and_close(s, txn->status, http_error_message(s));
DBG_TRACE_DEVEL("leaving on error",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA|STRM_EV_HTTP_ERR, s, txn);
return 0;
}
/* This stream analyser runs all HTTP request processing which is common to
* frontends and backends, which means blocking ACLs, filters, connection-close,
* reqadd, stats and redirects. This is performed for the designated proxy.
* It returns 1 if the processing can continue on next analysers, or zero if it
* either needs more data or wants to immediately abort the request (eg: deny,
* error, ...).
*/
int http_process_req_common(struct stream *s, struct channel *req, int an_bit, struct proxy *px)
{
struct list *def_rules, *rules;
struct session *sess = s->sess;
struct http_txn *txn = s->txn;
struct http_msg *msg = &txn->req;
struct htx *htx;
struct redirect_rule *rule;
enum rule_result verdict;
struct connection *conn = objt_conn(sess->origin);
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, msg);
htx = htxbuf(&req->buf);
/* just in case we have some per-backend tracking. Only called the first
* execution of the analyser. */
if (!s->current_rule && !s->current_rule_list)
stream_inc_be_http_req_ctr(s);
def_rules = ((px->defpx && (an_bit == AN_REQ_HTTP_PROCESS_FE || px != sess->fe)) ? &px->defpx->http_req_rules : NULL);
rules = &px->http_req_rules;
/* evaluate http-request rules */
if ((def_rules && !LIST_ISEMPTY(def_rules)) || !LIST_ISEMPTY(rules)) {
verdict = http_req_get_intercept_rule(px, def_rules, rules, s);
switch (verdict) {
case HTTP_RULE_RES_YIELD: /* some data miss, call the function later. */
goto return_prx_yield;
case HTTP_RULE_RES_CONT:
case HTTP_RULE_RES_STOP: /* nothing to do */
break;
case HTTP_RULE_RES_DENY: /* deny or tarpit */
if (txn->flags & TX_CLTARPIT)
goto tarpit;
goto deny;
case HTTP_RULE_RES_ABRT: /* abort request, response already sent. Eg: auth */
goto return_prx_cond;
case HTTP_RULE_RES_DONE: /* OK, but terminate request processing (eg: redirect) */
goto done;
case HTTP_RULE_RES_BADREQ: /* failed with a bad request */
goto return_bad_req;
case HTTP_RULE_RES_ERROR: /* failed with a bad request */
goto return_int_err;
}
}
if (px->options2 & (PR_O2_RSTRICT_REQ_HDR_NAMES_BLK|PR_O2_RSTRICT_REQ_HDR_NAMES_DEL)) {
verdict = http_req_restrict_header_names(s, htx, px);
if (verdict == HTTP_RULE_RES_DENY)
goto deny;
}
if (conn && (conn->flags & CO_FL_EARLY_DATA) &&
(conn->flags & (CO_FL_EARLY_SSL_HS | CO_FL_SSL_WAIT_HS))) {
struct http_hdr_ctx ctx;
ctx.blk = NULL;
if (!http_find_header(htx, ist("Early-Data"), &ctx, 0)) {
if (unlikely(!http_add_header(htx, ist("Early-Data"), ist("1"))))
goto return_fail_rewrite;
}
}
/* OK at this stage, we know that the request was accepted according to
* the http-request rules, we can check for the stats. Note that the
* URI is detected *before* the req* rules in order not to be affected
* by a possible reqrep, while they are processed *after* so that a
* reqdeny can still block them. This clearly needs to change in 1.6!
*/
if (!s->target && http_stats_check_uri(s, txn, px)) {
s->target = &http_stats_applet.obj_type;
if (unlikely(!sc_applet_create(s->scb, objt_applet(s->target)))) {
s->logs.request_ts = now_ns;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_RESOURCE;
goto return_int_err;
}
/* parse the whole stats request and extract the relevant information */
http_handle_stats(s, req, px);
verdict = http_req_get_intercept_rule(px, NULL, &px->uri_auth->http_req_rules, s);
/* not all actions implemented: deny, allow, auth */
if (verdict == HTTP_RULE_RES_DENY) /* stats http-request deny */
goto deny;
if (verdict == HTTP_RULE_RES_ABRT) /* stats auth / stats http-request auth */
goto return_prx_cond;
if (verdict == HTTP_RULE_RES_BADREQ) /* failed with a bad request */
goto return_bad_req;
if (verdict == HTTP_RULE_RES_ERROR) /* failed with a bad request */
goto return_int_err;
}
/* Proceed with the applets now. */
if (unlikely(objt_applet(s->target))) {
if (sess->fe == s->be) /* report it if the request was intercepted by the frontend */
_HA_ATOMIC_INC(&sess->fe->fe_counters.intercepted_req);
if (http_handle_expect_hdr(s, htx, msg) == -1)
goto return_int_err;
if (!(s->flags & SF_ERR_MASK)) // this is not really an error but it is
s->flags |= SF_ERR_LOCAL; // to mark that it comes from the proxy
http_set_term_flags(s);
if (HAS_FILTERS(s))
req->analysers |= AN_REQ_FLT_HTTP_HDRS;
/* enable the minimally required analyzers to handle keep-alive and compression on the HTTP response */
req->analysers &= (AN_REQ_HTTP_BODY | AN_REQ_FLT_HTTP_HDRS | AN_REQ_FLT_END);
req->analysers &= ~AN_REQ_FLT_XFER_DATA;
req->analysers |= AN_REQ_HTTP_XFER_BODY;
s->scb->flags |= SC_FL_SND_ASAP;
s->flags |= SF_ASSIGNED;
goto done;
}
/* check whether we have some ACLs set to redirect this request */
list_for_each_entry(rule, &px->redirect_rules, list) {
if (rule->cond) {
int ret;
ret = acl_exec_cond(rule->cond, px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
ret = acl_pass(ret);
if (rule->cond->pol == ACL_COND_UNLESS)
ret = !ret;
if (!ret)
continue;
}
if (!http_apply_redirect_rule(rule, s, txn))
goto return_int_err;
goto done;
}
/* POST requests may be accompanied with an "Expect: 100-Continue" header.
* If this happens, then the data will not come immediately, so we must
* send all what we have without waiting. Note that due to the small gain
* in waiting for the body of the request, it's easier to simply put the
* SC_FL_SND_ASAP flag on the back SC any time. It's a one-shot flag so it
* will remove itself once used.
*/
s->scb->flags |= SC_FL_SND_ASAP;
done: /* done with this analyser, continue with next ones that the calling
* points will have set, if any.
*/
req->analyse_exp = TICK_ETERNITY;
done_without_exp: /* done with this analyser, but don't reset the analyse_exp. */
req->analysers &= ~an_bit;
s->current_rule = s->current_rule_list = NULL;
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 1;
tarpit:
/* Allow cookie logging
*/
if (s->be->cookie_name || sess->fe->capture_name)
http_manage_client_side_cookies(s, req);
/* When a connection is tarpitted, we use the tarpit timeout,
* which may be the same as the connect timeout if unspecified.
* If unset, then set it to zero because we really want it to
* eventually expire. We build the tarpit as an analyser.
*/
channel_htx_erase(&s->req, htx);
/* wipe the request out so that we can drop the connection early
* if the client closes first.
*/
channel_dont_connect(req);
req->analysers &= AN_REQ_FLT_END; /* remove switching rules etc... */
req->analysers |= AN_REQ_HTTP_TARPIT;
req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit);
if (!req->analyse_exp)
req->analyse_exp = tick_add(now_ms, 0);
stream_inc_http_err_ctr(s);
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.denied_req);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->denied_req);
goto done_without_exp;
deny: /* this request was blocked (denied) */
/* Allow cookie logging
*/
if (s->be->cookie_name || sess->fe->capture_name)
http_manage_client_side_cookies(s, req);
s->logs.request_ts = now_ns;
stream_inc_http_err_ctr(s);
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.denied_req);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->denied_req);
goto return_prx_err;
return_fail_rewrite:
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
if (objt_server(s->target))
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
/* fall through */
return_int_err:
txn->status = 500;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_INTERNAL;
_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.internal_errors);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->internal_errors);
goto return_prx_err;
return_bad_req:
txn->status = 400;
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->failed_req);
/* fall through */
return_prx_err:
http_set_term_flags(s);
http_reply_and_close(s, txn->status, http_error_message(s));
/* fall through */
return_prx_cond:
http_set_term_flags(s);
req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY;
s->current_rule = s->current_rule_list = NULL;
DBG_TRACE_DEVEL("leaving on error",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA|STRM_EV_HTTP_ERR, s, txn);
return 0;
return_prx_yield:
channel_dont_connect(req);
DBG_TRACE_DEVEL("waiting for more data",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
}
/* This function performs all the processing enabled for the current request.
* It returns 1 if the processing can continue on next analysers, or zero if it
* needs more data, encounters an error, or wants to immediately abort the
* request. It relies on buffers flags, and updates s->req.analysers.
*/
int http_process_request(struct stream *s, struct channel *req, int an_bit)
{
struct session *sess = s->sess;
struct http_txn *txn = s->txn;
struct htx *htx;
struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
/*
* Right now, we know that we have processed the entire headers
* and that unwanted requests have been filtered out. We can do
* whatever we want with the remaining request. Also, now we
* may have separate values for ->fe, ->be.
*/
htx = htxbuf(&req->buf);
/*
* 7: Now we can work with the cookies.
* Note that doing so might move headers in the request, but
* the fields will stay coherent and the URI will not move.
* This should only be performed in the backend.
*/
if (s->be->cookie_name || sess->fe->capture_name)
http_manage_client_side_cookies(s, req);
/* 8: Generate unique ID if a "unique-id-format" is defined.
*
* A unique ID is generated even when it is not sent to ensure that the ID can make use of
* fetches only available in the HTTP request processing stage.
*/
if (!lf_expr_isempty(&sess->fe->format_unique_id)) {
struct ist unique_id = stream_generate_unique_id(s, &sess->fe->format_unique_id);
if (!isttest(unique_id)) {
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_RESOURCE;
goto return_int_err;
}
/* send unique ID if a "unique-id-header" is defined */
if (isttest(sess->fe->header_unique_id) &&
unlikely(!http_add_header(htx, sess->fe->header_unique_id, unique_id)))
goto return_fail_rewrite;
}
/* handle http extensions (if configured) */
if (unlikely(!http_handle_7239_header(s, req)))
goto return_fail_rewrite;
if (unlikely(!http_handle_xff_header(s, req)))
goto return_fail_rewrite;
if (unlikely(!http_handle_xot_header(s, req)))
goto return_fail_rewrite;
/* Filter the request headers if there are filters attached to the
* stream.
*/
if (HAS_FILTERS(s))
req->analysers |= AN_REQ_FLT_HTTP_HDRS;
/* If we have no server assigned yet and we're balancing on url_param
* with a POST request, we may be interested in checking the body for
* that parameter. This will be done in another analyser.
*/
if (!(s->flags & (SF_ASSIGNED|SF_DIRECT)) &&
s->txn->meth == HTTP_METH_POST &&
(s->be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_PH) {
channel_dont_connect(req);
req->analysers |= AN_REQ_HTTP_BODY;
}
req->analysers &= ~AN_REQ_FLT_XFER_DATA;
req->analysers |= AN_REQ_HTTP_XFER_BODY;
/* We expect some data from the client. Unless we know for sure
* we already have a full request, we have to re-enable quick-ack
* in case we previously disabled it, otherwise we might cause
* the client to delay further data.
*/
if ((sess->listener && (sess->listener->bind_conf->options & BC_O_NOQUICKACK)) && !(htx->flags & HTX_FL_EOM))
conn_set_quickack(cli_conn, 1);
/*************************************************************
* OK, that's finished for the headers. We have done what we *
* could. Let's switch to the DATA state. *
************************************************************/
req->analyse_exp = TICK_ETERNITY;
req->analysers &= ~an_bit;
s->logs.request_ts = now_ns;
/* OK let's go on with the BODY now */
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 1;
return_fail_rewrite:
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
if (objt_server(s->target))
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
/* fall through */
return_int_err:
txn->status = 500;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_INTERNAL;
_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.internal_errors);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->internal_errors);
http_set_term_flags(s);
http_reply_and_close(s, txn->status, http_error_message(s));
DBG_TRACE_DEVEL("leaving on error",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA|STRM_EV_HTTP_ERR, s, txn);
return 0;
}
/* This function is an analyser which processes the HTTP tarpit. It always
* returns zero, at the beginning because it prevents any other processing
* from occurring, and at the end because it terminates the request.
*/
int http_process_tarpit(struct stream *s, struct channel *req, int an_bit)
{
struct http_txn *txn = s->txn;
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, &txn->req);
/* This connection is being tarpitted. The CLIENT side has
* already set the connect expiration date to the right
* timeout. We just have to check that the client is still
* there and that the timeout has not expired.
*/
channel_dont_connect(req);
if (!(s->scf->flags & (SC_FL_ABRT_DONE|SC_FL_EOS)) &&
!tick_is_expired(req->analyse_exp, now_ms)) {
/* Be sure to drain all data from the request channel */
channel_htx_erase(req, htxbuf(&req->buf));
DBG_TRACE_DEVEL("waiting for tarpit timeout expiry",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
}
/* We will set the queue timer to the time spent, just for
* logging purposes. We fake a 500 server error, so that the
* attacker will not suspect his connection has been tarpitted.
* It will not cause trouble to the logs because we can exclude
* the tarpitted connections by filtering on the 'PT' status flags.
*/
s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts);
http_set_term_flags(s);
http_reply_and_close(s, txn->status, (!(s->scf->flags & SC_FL_ERROR) ? http_error_message(s) : NULL));
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
}
/* This function is an analyser which waits for the HTTP request body. It waits
* for either the buffer to be full, or the full advertised contents to have
* reached the buffer. It must only be called after the standard HTTP request
* processing has occurred, because it expects the request to be parsed and will
* look for the Expect header. It may send a 100-Continue interim response. It
* returns zero if it needs to read more data, or 1 once it has completed its
* analysis.
*/
int http_wait_for_request_body(struct stream *s, struct channel *req, int an_bit)
{
struct session *sess = s->sess;
struct http_txn *txn = s->txn;
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, &s->txn->req);
switch (http_wait_for_msg_body(s, req, s->be->timeout.httpreq, 0)) {
case HTTP_RULE_RES_CONT:
goto http_end;
case HTTP_RULE_RES_YIELD:
goto missing_data_or_waiting;
case HTTP_RULE_RES_BADREQ:
goto return_bad_req;
case HTTP_RULE_RES_ERROR:
goto return_int_err;
case HTTP_RULE_RES_ABRT:
goto return_prx_cond;
default:
goto return_int_err;
}
http_end:
/* The situation will not evolve, so let's give up on the analysis. */
s->logs.request_ts = now_ns; /* update the request timer to reflect full request */
req->analysers &= ~an_bit;
req->analyse_exp = TICK_ETERNITY;
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 1;
missing_data_or_waiting:
channel_dont_connect(req);
DBG_TRACE_DEVEL("waiting for more data",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
return_int_err:
txn->status = 500;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_INTERNAL;
_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_INC(&s->be->be_counters.internal_errors);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->internal_errors);
goto return_prx_err;
return_bad_req: /* let's centralize all bad requests */
txn->status = 400;
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
if (sess->listener && sess->listener->counters)
_HA_ATOMIC_INC(&sess->listener->counters->failed_req);
/* fall through */
return_prx_err:
http_set_term_flags(s);
http_reply_and_close(s, txn->status, http_error_message(s));
/* fall through */
return_prx_cond:
http_set_term_flags(s);
req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY;
DBG_TRACE_DEVEL("leaving on error",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA|STRM_EV_HTTP_ERR, s, txn);
return 0;
}
/* This function is an analyser which forwards request body (including chunk
* sizes if any). It is called as soon as we must forward, even if we forward
* zero byte. The only situation where it must not be called is when we're in
* tunnel mode and we want to forward till the close. It's used both to forward
* remaining data and to resync after end of body. It expects the msg_state to
* be between MSG_BODY and MSG_DONE (inclusive). It returns zero if it needs to
* read more data, or 1 once we can go on with next request or end the stream.
* When in MSG_DATA or MSG_TRAILERS, it will automatically forward chunk_len
* bytes of pending data + the headers if not already done.
*/
int http_request_forward_body(struct stream *s, struct channel *req, int an_bit)
{
struct session *sess = s->sess;
struct http_txn *txn = s->txn;
struct http_msg *msg = &txn->req;
struct htx *htx;
short status = 0;
int ret;
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, msg);
htx = htxbuf(&req->buf);
if (htx->flags & HTX_FL_PARSING_ERROR)
goto return_bad_req;
if (htx->flags & HTX_FL_PROCESSING_ERROR)
goto return_int_err;
/* Note that we don't have to send 100-continue back because we don't
* need the data to complete our job, and it's up to the server to
* decide whether to return 100, 417 or anything else in return of
* an "Expect: 100-continue" header.
*/
if (msg->msg_state == HTTP_MSG_BODY)
msg->msg_state = HTTP_MSG_DATA;
/* in most states, we should abort in case of early close */
channel_auto_close(req);
if (req->to_forward) {
if (req->to_forward == CHN_INFINITE_FORWARD) {
if (s->scf->flags & SC_FL_EOI)
msg->msg_state = HTTP_MSG_ENDING;
}
else {
/* We can't process the buffer's contents yet */
req->flags |= CF_WAKE_WRITE;
goto missing_data_or_waiting;
}
}
if (msg->msg_state >= HTTP_MSG_ENDING)
goto ending;
if (txn->meth == HTTP_METH_CONNECT) {
msg->msg_state = HTTP_MSG_ENDING;
goto ending;
}
/* Forward input data. We get it by removing all outgoing data not
* forwarded yet from HTX data size. If there are some data filters, we
* let them decide the amount of data to forward.
*/
if (HAS_REQ_DATA_FILTERS(s)) {
ret = flt_http_payload(s, msg, htx->data);
if (ret < 0)
goto return_bad_req;
c_adv(req, ret);
}
else {
c_adv(req, htx->data - co_data(req));
if ((global.tune.options & GTUNE_USE_FAST_FWD) && (msg->flags & HTTP_MSGF_XFER_LEN))
channel_htx_forward_forever(req, htx);
}
if (htx->data != co_data(req))
goto missing_data_or_waiting;
/* Check if the end-of-message is reached and if so, switch the message
* in HTTP_MSG_ENDING state. Then if all data was marked to be
* forwarded, set the state to HTTP_MSG_DONE.
*/
if (!(htx->flags & HTX_FL_EOM))
goto missing_data_or_waiting;
msg->msg_state = HTTP_MSG_ENDING;
ending:
s->scb->flags &= ~SC_FL_SND_EXP_MORE; /* no more data are expected to be send */
/* other states, ENDING...TUNNEL */
if (msg->msg_state >= HTTP_MSG_DONE)
goto done;
if (HAS_REQ_DATA_FILTERS(s)) {
ret = flt_http_end(s, msg);
if (ret <= 0) {
if (!ret)
goto missing_data_or_waiting;
goto return_bad_req;
}
}
if (txn->meth == HTTP_METH_CONNECT)
msg->msg_state = HTTP_MSG_TUNNEL;
else {
msg->msg_state = HTTP_MSG_DONE;
req->to_forward = 0;
}
done:
/* we don't want to forward closes on DONE except in tunnel mode. */
if (!(txn->flags & TX_CON_WANT_TUN))
channel_dont_close(req);
if ((s->scb->flags & SC_FL_SHUT_DONE) && co_data(req)) {
/* request errors are most likely due to the server aborting the
* transfer.Bit handle server aborts only if there is no
* response. Otherwise, let a change to forward the response
* first.
*/
if (htx_is_empty(htxbuf(&s->res.buf)))
goto return_srv_abort;
}
http_end_request(s);
if (!(req->analysers & an_bit)) {