Merge ~athos/ubuntu/+source/haproxy:MRE-focal into ubuntu/+source/haproxy:ubuntu/focal-devel
- Git
- lp:~athos/ubuntu/+source/haproxy
- MRE-focal
- Merge into ubuntu/focal-devel
| Status: | Merged |
|---|---|
| Approved by: | git-ubuntu bot |
| Approved revision: | not available |
| Merged at revision: | 43025670639764462f3d8e1de02bccfe3c3b685a |
| Proposed branch: | ~athos/ubuntu/+source/haproxy:MRE-focal |
| Merge into: | ubuntu/+source/haproxy:ubuntu/focal-devel |
| Diff against target: |
2349 lines (+636/-305) 45 files modified
.github/workflows/cross-zoo.yml (+1/-1) .github/workflows/vtest.yml (+1/-0) .gitignore (+1/-0) CHANGELOG (+61/-0) SUBVERS (+1/-1) VERDATE (+2/-2) VERSION (+1/-1) debian/changelog (+19/-0) debian/patches/series (+0/-2) dev/null (+0/-150) doc/configuration.txt (+37/-14) include/common/h2.h (+2/-2) include/common/ist.h (+53/-0) include/common/time.h (+2/-1) include/types/spoe.h (+1/-0) reg-tests/http-messaging/h1_to_h1.vtc (+26/-0) reg-tests/http-messaging/h2_to_h1.vtc (+60/-0) reg-tests/http-rules/fragment_in_uri.vtc (+35/-0) reg-tests/http-rules/h1or2_to_h1c.vtc (+12/-4) scripts/publish-release (+3/-0) src/checks.c (+10/-1) src/chunk.c (+7/-3) src/debug.c (+3/-2) src/filters.c (+2/-3) src/flt_spoe.c (+21/-9) src/h1.c (+36/-7) src/h2.c (+76/-13) src/haproxy.c (+18/-1) src/hlua.c (+17/-3) src/http.c (+1/-1) src/http_msg.c (+10/-3) src/log.c (+4/-0) src/mux_h1.c (+15/-3) src/mux_h2.c (+15/-5) src/mworker.c (+21/-5) src/namespace.c (+1/-0) src/proto_htx.c (+2/-1) src/proto_tcp.c (+3/-1) src/proxy.c (+2/-2) src/sample.c (+2/-2) src/server.c (+31/-58) src/ssl_sock.c (+1/-1) src/stream_interface.c (+16/-0) src/tcp_rules.c (+2/-2) src/time.c (+2/-1) |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| git-ubuntu bot | Approve | ||
| Sergio Durigan Junior (community) | Approve | ||
| Canonical Server Reporter | Pending | ||
|
Review via email:
|
|||
Commit message
Description of the change
HAProxy MRE introducing the newest point release version for focal.
All relevant information is available at the MRE bug in LP: #2028418.
PPA: https:/
DEP8 test results:
- haproxy/
+ ✅ haproxy on focal for amd64 @ 31.10.23 23:06:37 Log️ 🗒️
+ ✅ haproxy on focal for arm64 @ 31.10.23 23:03:26 Log️ 🗒️
+ ✅ haproxy on focal for armhf @ 31.10.23 23:02:18 Log️ 🗒️
+ ✅ haproxy on focal for ppc64el @ 31.10.23 22:59:11 Log️ 🗒️
+ ✅ haproxy on focal for s390x @ 31.10.23 23:00:08 Log️ 🗒️
| Sergio Durigan Junior (sergiodj) wrote : | # |
| Sergio Durigan Junior (sergiodj) wrote : | # |
Athos was kind enough to patch the git repo and rerun the "Spec Compliance" test, which passed:
https:/
Therefore, I'm approving this MP. Thanks again!
| Sergio Durigan Junior (sergiodj) wrote : | # |
Ops, Athos correctly told me that the "Spec Compliance" test only applies to haproxy-2.4, which will be MRE'd to Jammy. This means that the results I posted above do not apply to this MP. That doesn't change my approval, though.
| git-ubuntu bot (git-ubuntu-bot) wrote : | # |
Approvers: athos-ribeiro, sergiodj
Uploaders: athos-ribeiro, sergiodj
MP auto-approved
| Athos Ribeiro (athos) wrote : | # |
Thanks, Sergio!
Uploaded!
Preview Diff
| 1 | diff --git a/.github/workflows/cross-zoo.yml b/.github/workflows/cross-zoo.yml |
| 2 | index e2a5816..f2c8d7a 100644 |
| 3 | --- a/.github/workflows/cross-zoo.yml |
| 4 | +++ b/.github/workflows/cross-zoo.yml |
| 5 | @@ -97,7 +97,7 @@ jobs: |
| 6 | sudo apt-get -yq --force-yes install \ |
| 7 | gcc-${{ matrix.platform.arch }} \ |
| 8 | ${{ matrix.platform.libs }} |
| 9 | - - uses: actions/checkout@v2 |
| 10 | + - uses: actions/checkout@v3 |
| 11 | |
| 12 | |
| 13 | - name: install quictls |
| 14 | diff --git a/.github/workflows/vtest.yml b/.github/workflows/vtest.yml |
| 15 | index b919418..a12e4e1 100644 |
| 16 | --- a/.github/workflows/vtest.yml |
| 17 | +++ b/.github/workflows/vtest.yml |
| 18 | @@ -147,3 +147,4 @@ jobs: |
| 19 | sudo cat $asan |
| 20 | echo "::endgroup::" |
| 21 | done |
| 22 | + exit 1 |
| 23 | diff --git a/.gitignore b/.gitignore |
| 24 | index 3a760af..e5d132e 100644 |
| 25 | --- a/.gitignore |
| 26 | +++ b/.gitignore |
| 27 | @@ -35,6 +35,7 @@ |
| 28 | *.rej |
| 29 | *.orig |
| 30 | *.bak |
| 31 | +*.sw[op] |
| 32 | # And reject some specific files |
| 33 | /contrib/base64/base64rev |
| 34 | /contrib/halog/halog |
| 35 | diff --git a/CHANGELOG b/CHANGELOG |
| 36 | index 4b5713f..305eea6 100644 |
| 37 | --- a/CHANGELOG |
| 38 | +++ b/CHANGELOG |
| 39 | @@ -1,6 +1,67 @@ |
| 40 | ChangeLog : |
| 41 | =========== |
| 42 | |
| 43 | +2023/08/19 : 2.0.33 |
| 44 | + - BUG/MINOR: cfgparse-tcp: leak when re-declaring interface from bind line |
| 45 | + - BUG/MINOR: proxy: add missing interface bind free in free_proxy |
| 46 | + - BUG/MINOR: server: inherit from netns in srv_settings_cpy() |
| 47 | + - BUG/MINOR: namespace: missing free in netns_sig_stop() |
| 48 | + - BUG/MEDIUM: mworker: increase maxsock with each new worker |
| 49 | + - DOC: Add tune.h2.max-frame-size option to table of contents |
| 50 | + - BUG/MINOR: sample: Fix wrong overflow detection in add/sub conveters |
| 51 | + - BUG/MINOR: http: Return the right reason for 302 |
| 52 | + - CI: explicitely highlight VTest result section if there's something |
| 53 | + - DOC: configuration: describe Td in Timing events |
| 54 | + - BUG/MINOR: chunk: fix chunk_appendf() to not write a zero if buffer is full |
| 55 | + - BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value replacement |
| 56 | + - BUG/MAJOR: http: reject any empty content-length header value |
| 57 | + - MINOR: ist: add new function ist_find_range() to find a character range |
| 58 | + - MINOR: ist: Add istend() function to return a pointer to the end of the string |
| 59 | + - MINOR: http: add new function http_path_has_forbidden_char() |
| 60 | + - MINOR: h2: pass accept-invalid-http-request down the request parser |
| 61 | + - BUG/MINOR: h1: do not accept '#' as part of the URI component |
| 62 | + - BUG/MINOR: h2: reject more chars from the :path pseudo header |
| 63 | + - REGTESTS: http-rules: verify that we block '#' by default for normalize-uri |
| 64 | + - DOC: clarify the handling of URL fragments in requests |
| 65 | + - BUG/MINOR: http: skip leading zeroes in content-length values |
| 66 | + |
| 67 | +2023/06/12 : 2.0.32 |
| 68 | + - BUG/MEDIUM: mworker: don't register mworker_accept_wrapper() when master FD is wrong |
| 69 | + - BUG/MINOR: mworker: prevent incorrect values in uptime |
| 70 | + - BUG/MINOR: ssl: Use 'date' instead of 'now' in ocsp stapling callback |
| 71 | + - BUG/MINOR: mux-h2: make sure the h2c task exists before refreshing it |
| 72 | + - BUG/MEDIUM: spoe: Don't set the default traget for the SPOE agent frontend |
| 73 | + - BUG/MEDIUM: mux-h2: erase h2c->wait_event.tasklet on error path |
| 74 | + - BUG/MEDIUM: mux-h1: Wakeup H1C on shutw if there is no I/O subscription |
| 75 | + - MINOR: proxy/pool: prevent unnecessary calls to pool_gc() |
| 76 | + - CI: bump "actions/checkout" to v3 for cross zoo matrix |
| 77 | + - BUG/MEDIUM: Update read expiration date on synchronous send |
| 78 | + - BUG/MINOR: mux-h2: make sure to produce a log on invalid requests |
| 79 | + - MINOR: checks: make sure spread-checks is used also at boot time |
| 80 | + - MINOR: clock: measure the total boot time |
| 81 | + - BUG/MINOR: checks: postpone the startup of health checks by the boot time |
| 82 | + - BUG/MEDIUM: mux-h1: Handle connection error after a synchronous send |
| 83 | + - BUG/MINOR: mux-h1: Account consumed output data on synchronous connection error |
| 84 | + - BUG/MEDIUM: mux-h1: do not refrain from signaling errors after end of input |
| 85 | + - BUG/MINOR: tcp-rules: Don't shortened the inspect-delay when EOI is set |
| 86 | + - DOC: config: Clarify conditions to shorten the inspect-delay for TCP rules |
| 87 | + - BUG/MINOR: hlua: unsafe hlua_lua2smp() usage |
| 88 | + - SCRIPTS: publish-release: update the umask to keep group write access |
| 89 | + - BUG/MINOR: log: fix memory error handling in parse_logsrv() |
| 90 | + - BUG/MINOR: proxy: missing free in free_proxy for redirect rules |
| 91 | + - MINOR: spoe: Don't stop disabled proxies |
| 92 | + - BUG/MEDIUM: filters: Don't deinit filters for disabled proxies during startup |
| 93 | + - BUG/MINOR: debug: do not emit empty lines in thread dumps |
| 94 | + - BUG/MEDIUM: spoe: Don't start new applet if there are enough idle ones |
| 95 | + - BUG/MINOR: server: incorrect report for tracking servers leaving drain |
| 96 | + - MINOR: server: explicitly commit state change in srv_update_status() |
| 97 | + - BUG/MINOR: server: don't miss proxy stats update on server state transitions |
| 98 | + - BUG/MINOR: server: don't miss server stats update on server state transitions |
| 99 | + - BUG/MINOR: server: don't use date when restoring last_change from state file |
| 100 | + - DOC: config: Fix bind/server/peer documentation in the peers section |
| 101 | + - CONTRIB: Add vi file extensions to .gitignore |
| 102 | + - BUG/MINOR: spoe: Only skip sending new frame after a receive attempt |
| 103 | + |
| 104 | 2023/02/14 : 2.0.31 |
| 105 | - SCRIPTS: announce-release: add a link to the data plane API |
| 106 | - CI: github: change "ubuntu-latest" to "ubuntu-20.04" |
| 107 | diff --git a/SUBVERS b/SUBVERS |
| 108 | index e1824da..a32020b 100644 |
| 109 | --- a/SUBVERS |
| 110 | +++ b/SUBVERS |
| 111 | @@ -1,2 +1,2 @@ |
| 112 | --c8b1c15 |
| 113 | +-83002d4 |
| 114 | |
| 115 | diff --git a/VERDATE b/VERDATE |
| 116 | index 2f36592..cb54148 100644 |
| 117 | --- a/VERDATE |
| 118 | +++ b/VERDATE |
| 119 | @@ -1,2 +1,2 @@ |
| 120 | -2023-02-14 16:58:28 +0100 |
| 121 | -2023/02/14 |
| 122 | +2023-08-19 11:32:22 +0200 |
| 123 | +2023/08/19 |
| 124 | diff --git a/VERSION b/VERSION |
| 125 | index e235558..520d2d2 100644 |
| 126 | --- a/VERSION |
| 127 | +++ b/VERSION |
| 128 | @@ -1 +1 @@ |
| 129 | -2.0.31 |
| 130 | +2.0.33 |
| 131 | diff --git a/debian/changelog b/debian/changelog |
| 132 | index 1d940f1..8866696 100644 |
| 133 | --- a/debian/changelog |
| 134 | +++ b/debian/changelog |
| 135 | @@ -1,3 +1,22 @@ |
| 136 | +haproxy (2.0.33-0ubuntu0.1) focal; urgency=medium |
| 137 | + |
| 138 | + * New upstream release (LP: #2028418) |
| 139 | + - Major and critical bug fixes according to the upstream changelog: |
| 140 | + + BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value |
| 141 | + replacement |
| 142 | + + BUG/MAJOR: http: reject any empty content-length header value |
| 143 | + - For further information, refer to the upstream changelog at |
| 144 | + https://www.haproxy.org/download/2.0/src/CHANGELOG and to the upstream |
| 145 | + release announcements at |
| 146 | + https://www.mail-archive.com/haproxy@formilux.org/msg43668.html |
| 147 | + (2.0.32), and |
| 148 | + https://www.mail-archive.com/haproxy@formilux.org/msg43904.html (2.0.33) |
| 149 | + - Remove patches applied by upstream in debian/patches: |
| 150 | + + CVE-2023-40225-1.patch |
| 151 | + + CVE-2023-40225-2.patch |
| 152 | + |
| 153 | + -- Athos Ribeiro <athos.ribeiro@canonical.com> Tue, 31 Oct 2023 16:00:44 -0300 |
| 154 | + |
| 155 | haproxy (2.0.31-0ubuntu0.2) focal-security; urgency=medium |
| 156 | |
| 157 | * SECURITY UPDATE: incorrect handling of empty content-length header |
| 158 | diff --git a/debian/patches/CVE-2023-40225-1.patch b/debian/patches/CVE-2023-40225-1.patch |
| 159 | deleted file mode 100644 |
| 160 | index dd454a4..0000000 |
| 161 | --- a/debian/patches/CVE-2023-40225-1.patch |
| 162 | +++ /dev/null |
| 163 | @@ -1,284 +0,0 @@ |
| 164 | -From 4b400110a58da1aa58d58d6371875a3a64c0cff6 Mon Sep 17 00:00:00 2001 |
| 165 | -From: Willy Tarreau <w@1wt.eu> |
| 166 | -Date: Wed, 9 Aug 2023 08:32:48 +0200 |
| 167 | -Subject: [PATCH] BUG/MAJOR: http: reject any empty content-length header |
| 168 | - value |
| 169 | - |
| 170 | -The content-length header parser has its dedicated function, in order |
| 171 | -to take extreme care about invalid, unparsable, or conflicting values. |
| 172 | -But there's a corner case in it, by which it stops comparing values |
| 173 | -when reaching the end of the header. This has for a side effect that |
| 174 | -an empty value or a value that ends with a comma does not deserve |
| 175 | -further analysis, and it acts as if the header was absent. |
| 176 | - |
| 177 | -While this is not necessarily a problem for the value ending with a |
| 178 | -comma as it will be cause a header folding and will disappear, it is a |
| 179 | -problem for the first isolated empty header because this one will not |
| 180 | -be recontructed when next ones are seen, and will be passed as-is to the |
| 181 | -backend server. A vulnerable HTTP/1 server hosted behind haproxy that |
| 182 | -would just use this first value as "0" and ignore the valid one would |
| 183 | -then not be protected by haproxy and could be attacked this way, taking |
| 184 | -the payload for an extra request. |
| 185 | - |
| 186 | -In field the risk depends on the server. Most commonly used servers |
| 187 | -already have safe content-length parsers, but users relying on haproxy |
| 188 | -to protect a known-vulnerable server might be at risk (and the risk of |
| 189 | -a bug even in a reputable server should never be dismissed). |
| 190 | - |
| 191 | -A configuration-based work-around consists in adding the following rule |
| 192 | -in the frontend, to explicitly reject requests featuring an empty |
| 193 | -content-length header that would have not be folded into an existing |
| 194 | -one: |
| 195 | - |
| 196 | - http-request deny if { hdr_len(content-length) 0 } |
| 197 | - |
| 198 | -The real fix consists in adjusting the parser so that it always expects a |
| 199 | -value at the beginning of the header or after a comma. It will now reject |
| 200 | -requests and responses having empty values anywhere in the C-L header. |
| 201 | - |
| 202 | -This needs to be backported to all supported versions. Note that the |
| 203 | -modification was made to functions h1_parse_cont_len_header() and |
| 204 | -http_parse_cont_len_header(). Prior to 2.8 the latter was in |
| 205 | -h2_parse_cont_len_header(). One day the two should be refused but the |
| 206 | -former is also used by Lua. |
| 207 | - |
| 208 | -The HTTP messaging reg-tests were completed to test these cases. |
| 209 | - |
| 210 | -Thanks to Ben Kallus of Dartmouth College and Narf Industries for |
| 211 | -reporting this! (this is in GH #2237). |
| 212 | - |
| 213 | -(cherry picked from commit 6492f1f29d738457ea9f382aca54537f35f9d856) |
| 214 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 215 | -(cherry picked from commit a32f99f6f991d123ea3e307bf8aa63220836d365) |
| 216 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 217 | -(cherry picked from commit 65921ee12d88e9fb1fa9f6cd8198fd64b3a3f37f) |
| 218 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 219 | -(cherry picked from commit d17c50010d591d1c070e1cb0567a06032d8869e9) |
| 220 | -[wt: applied to h2_parse_cont_len_header() in src/h2.c instead] |
| 221 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 222 | -(cherry picked from commit ba9afd2774c03e434165475b537d0462801f49bb) |
| 223 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 224 | -(cherry picked from commit e8ba5e106444fc78558f4ff26e9ce946f89216f4) |
| 225 | -[wt: legacy not affected, http_wait_for_request() tests for empty |
| 226 | - value (cf. proto_http.c:1099)] |
| 227 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 228 | ---- |
| 229 | - reg-tests/http-messaging/h1_to_h1.vtc | 26 ++++++++++++++ |
| 230 | - reg-tests/http-messaging/h2_to_h1.vtc | 60 +++++++++++++++++++++++++++++++++ |
| 231 | - src/h1.c | 22 +++++++++--- |
| 232 | - src/h2.c | 22 +++++++++--- |
| 233 | - 4 files changed, 122 insertions(+), 8 deletions(-) |
| 234 | - |
| 235 | -diff --git a/reg-tests/http-messaging/h1_to_h1.vtc b/reg-tests/http-messaging/h1_to_h1.vtc |
| 236 | -index 7694014..aeb3a60 100644 |
| 237 | ---- a/reg-tests/http-messaging/h1_to_h1.vtc |
| 238 | -+++ b/reg-tests/http-messaging/h1_to_h1.vtc |
| 239 | -@@ -269,3 +269,29 @@ client c3h1 -connect ${h1_feh1_sock} { |
| 240 | - # arrive here. |
| 241 | - expect_close |
| 242 | - } -run |
| 243 | -+ |
| 244 | -+client c4h1 -connect ${h1_feh1_sock} { |
| 245 | -+ # this request is invalid and advertises an invalid C-L ending with an |
| 246 | -+ # empty value, which results in a stream error. |
| 247 | -+ txreq \ |
| 248 | -+ -req "GET" \ |
| 249 | -+ -url "/test31.html" \ |
| 250 | -+ -hdr "content-length: 0," \ |
| 251 | -+ -hdr "connection: close" |
| 252 | -+ rxresp |
| 253 | -+ expect resp.status == 400 |
| 254 | -+ expect_close |
| 255 | -+} -run |
| 256 | -+ |
| 257 | -+client c5h1 -connect ${h1_feh1_sock} { |
| 258 | -+ # this request is invalid and advertises an empty C-L, which results |
| 259 | -+ # in a stream error. |
| 260 | -+ txreq \ |
| 261 | -+ -req "GET" \ |
| 262 | -+ -url "/test41.html" \ |
| 263 | -+ -hdr "content-length:" \ |
| 264 | -+ -hdr "connection: close" |
| 265 | -+ rxresp |
| 266 | -+ expect resp.status == 400 |
| 267 | -+ expect_close |
| 268 | -+} -run |
| 269 | -diff --git a/reg-tests/http-messaging/h2_to_h1.vtc b/reg-tests/http-messaging/h2_to_h1.vtc |
| 270 | -index 481aded..3ed3751 100644 |
| 271 | ---- a/reg-tests/http-messaging/h2_to_h1.vtc |
| 272 | -+++ b/reg-tests/http-messaging/h2_to_h1.vtc |
| 273 | -@@ -10,6 +10,8 @@ barrier b1 cond 2 -cyclic |
| 274 | - barrier b2 cond 2 -cyclic |
| 275 | - barrier b3 cond 2 -cyclic |
| 276 | - barrier b4 cond 2 -cyclic |
| 277 | -+barrier b5 cond 2 -cyclic |
| 278 | -+barrier b6 cond 2 -cyclic |
| 279 | - |
| 280 | - server s1 { |
| 281 | - rxreq |
| 282 | -@@ -31,6 +33,12 @@ server s1 { |
| 283 | - |
| 284 | - barrier b4 sync |
| 285 | - # the next request is never received |
| 286 | -+ |
| 287 | -+ barrier b5 sync |
| 288 | -+ # the next request is never received |
| 289 | -+ |
| 290 | -+ barrier b6 sync |
| 291 | -+ # the next request is never received |
| 292 | - } -repeat 2 -start |
| 293 | - |
| 294 | - haproxy h1 -conf { |
| 295 | -@@ -115,6 +123,32 @@ client c1h2 -connect ${h1_feh2_sock} { |
| 296 | - txdata -data "this is sent and ignored" |
| 297 | - rxrst |
| 298 | - } -run |
| 299 | -+ |
| 300 | -+ # fifth request is invalid and advertises an invalid C-L ending with an |
| 301 | -+ # empty value, which results in a stream error. |
| 302 | -+ stream 9 { |
| 303 | -+ barrier b5 sync |
| 304 | -+ txreq \ |
| 305 | -+ -req "GET" \ |
| 306 | -+ -scheme "https" \ |
| 307 | -+ -url "/test5.html" \ |
| 308 | -+ -hdr "content-length" "0," \ |
| 309 | -+ -nostrend |
| 310 | -+ rxrst |
| 311 | -+ } -run |
| 312 | -+ |
| 313 | -+ # sixth request is invalid and advertises an empty C-L, which results |
| 314 | -+ # in a stream error. |
| 315 | -+ stream 11 { |
| 316 | -+ barrier b6 sync |
| 317 | -+ txreq \ |
| 318 | -+ -req "GET" \ |
| 319 | -+ -scheme "https" \ |
| 320 | -+ -url "/test6.html" \ |
| 321 | -+ -hdr "content-length" "" \ |
| 322 | -+ -nostrend |
| 323 | -+ rxrst |
| 324 | -+ } -run |
| 325 | - } -run |
| 326 | - |
| 327 | - # HEAD requests : don't work well yet |
| 328 | -@@ -257,4 +291,30 @@ client c3h2 -connect ${h1_feh2_sock} { |
| 329 | - txdata -data "this is sent and ignored" |
| 330 | - rxrst |
| 331 | - } -run |
| 332 | -+ |
| 333 | -+ # fifth request is invalid and advertises invalid C-L ending with an |
| 334 | -+ # empty value, which results in a stream error. |
| 335 | -+ stream 9 { |
| 336 | -+ barrier b5 sync |
| 337 | -+ txreq \ |
| 338 | -+ -req "POST" \ |
| 339 | -+ -scheme "https" \ |
| 340 | -+ -url "/test25.html" \ |
| 341 | -+ -hdr "content-length" "0," \ |
| 342 | -+ -nostrend |
| 343 | -+ rxrst |
| 344 | -+ } -run |
| 345 | -+ |
| 346 | -+ # sixth request is invalid and advertises an empty C-L, which results |
| 347 | -+ # in a stream error. |
| 348 | -+ stream 11 { |
| 349 | -+ barrier b6 sync |
| 350 | -+ txreq \ |
| 351 | -+ -req "POST" \ |
| 352 | -+ -scheme "https" \ |
| 353 | -+ -url "/test26.html" \ |
| 354 | -+ -hdr "content-length" "" \ |
| 355 | -+ -nostrend |
| 356 | -+ rxrst |
| 357 | -+ } -run |
| 358 | - } -run |
| 359 | -diff --git a/src/h1.c b/src/h1.c |
| 360 | -index 69a7ced..248e315 100644 |
| 361 | ---- a/src/h1.c |
| 362 | -+++ b/src/h1.c |
| 363 | -@@ -32,13 +32,20 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) |
| 364 | - int not_first = !!(h1m->flags & H1_MF_CLEN); |
| 365 | - struct ist word; |
| 366 | - |
| 367 | -- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment |
| 368 | -+ word.ptr = value->ptr; |
| 369 | - e = value->ptr + value->len; |
| 370 | - |
| 371 | -- while (++word.ptr < e) { |
| 372 | -- /* skip leading delimitor and blanks */ |
| 373 | -- if (unlikely(HTTP_IS_LWS(*word.ptr))) |
| 374 | -+ while (1) { |
| 375 | -+ if (word.ptr >= e) { |
| 376 | -+ /* empty header or empty value */ |
| 377 | -+ goto fail; |
| 378 | -+ } |
| 379 | -+ |
| 380 | -+ /* skip leading delimiter and blanks */ |
| 381 | -+ if (unlikely(HTTP_IS_LWS(*word.ptr))) { |
| 382 | -+ word.ptr++; |
| 383 | - continue; |
| 384 | -+ } |
| 385 | - |
| 386 | - /* digits only now */ |
| 387 | - for (cl = 0, n = word.ptr; n < e; n++) { |
| 388 | -@@ -77,6 +84,13 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) |
| 389 | - h1m->flags |= H1_MF_CLEN; |
| 390 | - h1m->curr_len = h1m->body_len = cl; |
| 391 | - *value = word; |
| 392 | -+ |
| 393 | -+ /* Now either n==e and we're done, or n points to the comma, |
| 394 | -+ * and we skip it and continue. |
| 395 | -+ */ |
| 396 | -+ if (n++ == e) |
| 397 | -+ break; |
| 398 | -+ |
| 399 | - word.ptr = n; |
| 400 | - } |
| 401 | - /* here we've reached the end with a single value or a series of |
| 402 | -diff --git a/src/h2.c b/src/h2.c |
| 403 | -index bfc0bda..77a9346 100644 |
| 404 | ---- a/src/h2.c |
| 405 | -+++ b/src/h2.c |
| 406 | -@@ -458,13 +458,20 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon |
| 407 | - int not_first = !!(*msgf & H2_MSGF_BODY_CL); |
| 408 | - struct ist word; |
| 409 | - |
| 410 | -- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment |
| 411 | -+ word.ptr = value->ptr; |
| 412 | - e = value->ptr + value->len; |
| 413 | - |
| 414 | -- while (++word.ptr < e) { |
| 415 | -- /* skip leading delimitor and blanks */ |
| 416 | -- if (unlikely(HTTP_IS_LWS(*word.ptr))) |
| 417 | -+ while (1) { |
| 418 | -+ if (word.ptr >= e) { |
| 419 | -+ /* empty header or empty value */ |
| 420 | -+ goto fail; |
| 421 | -+ } |
| 422 | -+ |
| 423 | -+ /* skip leading delimiter and blanks */ |
| 424 | -+ if (unlikely(HTTP_IS_LWS(*word.ptr))) { |
| 425 | -+ word.ptr++; |
| 426 | - continue; |
| 427 | -+ } |
| 428 | - |
| 429 | - /* digits only now */ |
| 430 | - for (cl = 0, n = word.ptr; n < e; n++) { |
| 431 | -@@ -503,6 +510,13 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon |
| 432 | - *msgf |= H2_MSGF_BODY_CL; |
| 433 | - *body_len = cl; |
| 434 | - *value = word; |
| 435 | -+ |
| 436 | -+ /* Now either n==e and we're done, or n points to the comma, |
| 437 | -+ * and we skip it and continue. |
| 438 | -+ */ |
| 439 | -+ if (n++ == e) |
| 440 | -+ break; |
| 441 | -+ |
| 442 | - word.ptr = n; |
| 443 | - } |
| 444 | - /* here we've reached the end with a single value or a series of |
| 445 | --- |
| 446 | -1.7.10.4 |
| 447 | - |
| 448 | diff --git a/debian/patches/CVE-2023-40225-2.patch b/debian/patches/CVE-2023-40225-2.patch |
| 449 | deleted file mode 100644 |
| 450 | index b4bfbc6..0000000 |
| 451 | --- a/debian/patches/CVE-2023-40225-2.patch |
| 452 | +++ /dev/null |
| 453 | @@ -1,150 +0,0 @@ |
| 454 | -From a90ecde20e53bb26a677b42f53db42900097af9a Mon Sep 17 00:00:00 2001 |
| 455 | -From: Willy Tarreau <w@1wt.eu> |
| 456 | -Date: Wed, 9 Aug 2023 11:02:34 +0200 |
| 457 | -Subject: [PATCH] BUG/MINOR: http: skip leading zeroes in content-length |
| 458 | - values |
| 459 | - |
| 460 | -Ben Kallus also noticed that we preserve leading zeroes on content-length |
| 461 | -values. While this is totally valid, it would be safer to at least trim |
| 462 | -them before passing the value, because a bogus server written to parse |
| 463 | -using "strtol(value, NULL, 0)" could inadvertently take a leading zero |
| 464 | -as a prefix for an octal value. While there is not much that can be done |
| 465 | -to protect such servers in general (e.g. lack of check for overflows etc), |
| 466 | -at least it's quite cheap to make sure the transmitted value is normalized |
| 467 | -and not taken for an octal one. |
| 468 | - |
| 469 | -This is not really a bug, rather a missed opportunity to sanitize the |
| 470 | -input, but is marked as a bug so that we don't forget to backport it to |
| 471 | -stable branches. |
| 472 | - |
| 473 | -A combined regtest was added to h1or2_to_h1c which already validates |
| 474 | -end-to-end syntax consistency on aggregate headers. |
| 475 | - |
| 476 | -(cherry picked from commit 22731762d9fe2c98d9e6c3942b1568266b23c69f) |
| 477 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 478 | -(cherry picked from commit c33738c7d4ed50e1758dbedd39dfe5bd929a5076) |
| 479 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 480 | -(cherry picked from commit 84462c9390c3efe95b748bb9fc3bd2edc3decd2f) |
| 481 | -Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> |
| 482 | -(cherry picked from commit e2e464018fda41758bd42d3bcd6ac623c88a110e) |
| 483 | -[wt: applied the http.c to h2.c] |
| 484 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 485 | -(cherry picked from commit c48acd15011dc6c66977bc088065693f430821df) |
| 486 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 487 | -(cherry picked from commit 0ac860d19ad68bf4c1f6f8f3721801b7079368c6) |
| 488 | -[wt: legacy mode was not changed as it's really not suitable for this, |
| 489 | - since by then headers were known from their start and length and we |
| 490 | - can't adjust them afterwards without breaking all indexing] |
| 491 | -Signed-off-by: Willy Tarreau <w@1wt.eu> |
| 492 | ---- |
| 493 | - reg-tests/http-rules/h1or2_to_h1c.vtc | 16 ++++++++++++---- |
| 494 | - src/h1.c | 8 ++++++++ |
| 495 | - src/h2.c | 8 ++++++++ |
| 496 | - 3 files changed, 28 insertions(+), 4 deletions(-) |
| 497 | - |
| 498 | ---- haproxy-2.0.31.orig/reg-tests/http-rules/h1or2_to_h1c.vtc |
| 499 | -+++ haproxy-2.0.31/reg-tests/http-rules/h1or2_to_h1c.vtc |
| 500 | -@@ -27,11 +27,11 @@ server s1 { |
| 501 | - -body "This is a body" |
| 502 | - |
| 503 | - expect req.method == "GET" |
| 504 | -- expect req.http.fe-sl1-crc == 992395575 |
| 505 | -- expect req.http.fe-sl2-crc == 1270056220 |
| 506 | -+ expect req.http.fe-sl1-crc == 1874847043 |
| 507 | -+ expect req.http.fe-sl2-crc == 1142278307 |
| 508 | - expect req.http.fe-hdr-crc == 1719311923 |
| 509 | -- expect req.http.be-sl1-crc == 2604236007 |
| 510 | -- expect req.http.be-sl2-crc == 4181358964 |
| 511 | -+ expect req.http.be-sl1-crc == 3455320059 |
| 512 | -+ expect req.http.be-sl2-crc == 2509326257 |
| 513 | - expect req.http.be-hdr-crc == 3634102538 |
| 514 | - } -repeat 2 -start |
| 515 | - |
| 516 | -@@ -53,6 +53,7 @@ haproxy h1 -conf { |
| 517 | - http-request set-var(req.path) path |
| 518 | - http-request set-var(req.query) query |
| 519 | - http-request set-var(req.param) url_param(qs_arg) |
| 520 | -+ http-request set-var(req.cl) req.fhdr(content-length) |
| 521 | - |
| 522 | - http-request set-header sl1 "sl1: " |
| 523 | - |
| 524 | -@@ -65,8 +66,10 @@ haproxy h1 -conf { |
| 525 | - |
| 526 | - http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;" |
| 527 | - http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>" |
| 528 | -+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>" |
| 529 | - http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; " |
| 530 | - http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>" |
| 531 | -+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>" |
| 532 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;" |
| 533 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;" |
| 534 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;" |
| 535 | -@@ -120,6 +123,7 @@ haproxy h1 -conf { |
| 536 | - http-request set-var(req.path) path |
| 537 | - http-request set-var(req.query) query |
| 538 | - http-request set-var(req.param) url_param(qs_arg) |
| 539 | -+ http-request set-var(req.cl) req.fhdr(content-length) |
| 540 | - |
| 541 | - http-request set-header sl1 "sl1: " |
| 542 | - |
| 543 | -@@ -132,8 +136,10 @@ haproxy h1 -conf { |
| 544 | - |
| 545 | - http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;" |
| 546 | - http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>" |
| 547 | -+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>" |
| 548 | - http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; " |
| 549 | - http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>" |
| 550 | -+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>" |
| 551 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;" |
| 552 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;" |
| 553 | - http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;" |
| 554 | -@@ -171,6 +177,7 @@ client c1h1 -connect ${h1_feh1_sock} { |
| 555 | - txreq \ |
| 556 | - -req GET \ |
| 557 | - -url /path/to/file.extension?qs_arg=qs_value \ |
| 558 | -+ -hdr "content-length: 000, 00" \ |
| 559 | - -hdr "hdr1: val1" \ |
| 560 | - -hdr "hdr2: val2a" \ |
| 561 | - -hdr "hdr2: val2b" \ |
| 562 | -@@ -205,6 +212,7 @@ client c1h2 -connect ${h1_feh2_sock} { |
| 563 | - -req GET \ |
| 564 | - -scheme "https" \ |
| 565 | - -url /path/to/file.extension?qs_arg=qs_value \ |
| 566 | -+ -hdr "content-length" "000, 00" \ |
| 567 | - -hdr "hdr1" "val1" \ |
| 568 | - -hdr "hdr2" " val2a" \ |
| 569 | - -hdr "hdr2" " val2b" \ |
| 570 | ---- haproxy-2.0.31.orig/src/h1.c |
| 571 | -+++ haproxy-2.0.31/src/h1.c |
| 572 | -@@ -56,6 +56,14 @@ int h1_parse_cont_len_header(struct h1m |
| 573 | - goto fail; |
| 574 | - break; |
| 575 | - } |
| 576 | -+ |
| 577 | -+ if (unlikely(!cl && n > word.ptr)) { |
| 578 | -+ /* There was a leading zero before this digit, |
| 579 | -+ * let's trim it. |
| 580 | -+ */ |
| 581 | -+ word.ptr = n; |
| 582 | -+ } |
| 583 | -+ |
| 584 | - if (unlikely(cl > ULLONG_MAX / 10ULL)) |
| 585 | - goto fail; /* multiply overflow */ |
| 586 | - cl = cl * 10ULL; |
| 587 | ---- haproxy-2.0.31.orig/src/h2.c |
| 588 | -+++ haproxy-2.0.31/src/h2.c |
| 589 | -@@ -482,6 +482,14 @@ int h2_parse_cont_len_header(unsigned in |
| 590 | - goto fail; |
| 591 | - break; |
| 592 | - } |
| 593 | -+ |
| 594 | -+ if (unlikely(!cl && n > word.ptr)) { |
| 595 | -+ /* There was a leading zero before this digit, |
| 596 | -+ * let's trim it. |
| 597 | -+ */ |
| 598 | -+ word.ptr = n; |
| 599 | -+ } |
| 600 | -+ |
| 601 | - if (unlikely(cl > ULLONG_MAX / 10ULL)) |
| 602 | - goto fail; /* multiply overflow */ |
| 603 | - cl = cl * 10ULL; |
| 604 | diff --git a/debian/patches/series b/debian/patches/series |
| 605 | index 339dd4f..0945bb0 100644 |
| 606 | --- a/debian/patches/series |
| 607 | +++ b/debian/patches/series |
| 608 | @@ -1,5 +1,3 @@ |
| 609 | 0002-Use-dpkg-buildflags-to-build-halog.patch |
| 610 | haproxy.service-start-after-syslog.patch |
| 611 | haproxy.service-add-documentation.patch |
| 612 | -CVE-2023-40225-1.patch |
| 613 | -CVE-2023-40225-2.patch |
| 614 | diff --git a/doc/configuration.txt b/doc/configuration.txt |
| 615 | index 4c7c59e..850f224 100644 |
| 616 | --- a/doc/configuration.txt |
| 617 | +++ b/doc/configuration.txt |
| 618 | @@ -3,7 +3,7 @@ |
| 619 | Configuration Manual |
| 620 | ---------------------- |
| 621 | version 2.0 |
| 622 | - 2023/02/14 |
| 623 | + 2023/08/19 |
| 624 | |
| 625 | |
| 626 | This document covers the configuration language as implemented in the version |
| 627 | @@ -650,6 +650,7 @@ The following keywords are supported in the "global" section : |
| 628 | - tune.h2.header-table-size |
| 629 | - tune.h2.initial-window-size |
| 630 | - tune.h2.max-concurrent-streams |
| 631 | + - tune.h2.max-frame-size |
| 632 | - tune.http.cookielen |
| 633 | - tune.http.logurilen |
| 634 | - tune.http.maxhdr |
| 635 | @@ -2088,7 +2089,8 @@ peers <peersect> |
| 636 | Creates a new peer list with name <peersect>. It is an independent section, |
| 637 | which is referenced by one or more stick-tables. |
| 638 | |
| 639 | -bind [<address>]:<port_range> [, ...] [param*] |
| 640 | +bind [<address>]:port [param*] |
| 641 | +bind /<path> [param*] |
| 642 | Defines the binding parameters of the local peer of this "peers" section. |
| 643 | Such lines are not supported with "peer" line in the same "peers" section. |
| 644 | |
| 645 | @@ -2117,15 +2119,16 @@ enabled |
| 646 | This re-enables a peers section which was previously disabled via the |
| 647 | "disabled" keyword. |
| 648 | |
| 649 | -peer <peername> <ip>:<port> [param*] |
| 650 | +peer <peername> [<address>]:port [param*] |
| 651 | +peer <peername> /<path> [param*] |
| 652 | Defines a peer inside a peers section. |
| 653 | If <peername> is set to the local peer name (by default hostname, or forced |
| 654 | using "-L" command line option), haproxy will listen for incoming remote peer |
| 655 | - connection on <ip>:<port>. Otherwise, <ip>:<port> defines where to connect to |
| 656 | - to join the remote peer, and <peername> is used at the protocol level to |
| 657 | - identify and validate the remote peer on the server side. |
| 658 | + connection on the provided address. Otherwise, the address defines where to |
| 659 | + connect to to join the remote peer, and <peername> is used at the protocol |
| 660 | + level to identify and validate the remote peer on the server side. |
| 661 | |
| 662 | - During a soft restart, local peer <ip>:<port> is used by the old instance to |
| 663 | + During a soft restart, local peer address is used by the old instance to |
| 664 | connect the new one and initiate a complete replication (teaching process). |
| 665 | |
| 666 | It is strongly recommended to have the exact same peers declaration on all |
| 667 | @@ -2139,12 +2142,13 @@ peer <peername> <ip>:<port> [param*] |
| 668 | Note: "peer" keyword may transparently be replaced by "server" keyword (see |
| 669 | "server" keyword explanation below). |
| 670 | |
| 671 | -server <peername> [<ip>:<port>] [param*] |
| 672 | +server <peername> [<address>:<port>] [param*] |
| 673 | +server <peername> [/<path>] [param*] |
| 674 | As previously mentioned, "peer" keyword may be replaced by "server" keyword |
| 675 | with a support for all "server" parameters found in 5.2 paragraph that are |
| 676 | - related to transport settings. If the underlying peer is local, <ip>:<port> |
| 677 | - parameters must not be present; these parameters must be provided on a "bind" |
| 678 | - line (see "bind" keyword of this "peers" section). |
| 679 | + related to transport settings. If the underlying peer is local, the address |
| 680 | + parameter must not be present; it must be provided on a "bind" line (see |
| 681 | + "bind" keyword of this "peers" section). |
| 682 | |
| 683 | A number of "server" parameters are irrelevant for "peers" sections. Peers by |
| 684 | nature do not support dynamic host name resolution nor health checks, hence |
| 685 | @@ -6082,7 +6086,8 @@ no option accept-invalid-http-request |
| 686 | remaining ones are blocked by default unless this option is enabled. This |
| 687 | option also relaxes the test on the HTTP version, it allows HTTP/0.9 requests |
| 688 | to pass through (no version specified) and multiple digits for both the major |
| 689 | - and the minor version. |
| 690 | + and the minor version. Finally, this option also allows incoming URLs to |
| 691 | + contain fragment references ('#' after the path). |
| 692 | |
| 693 | This option should never be enabled by default as it hides application bugs |
| 694 | and open security breaches. It should only be deployed after a problem has |
| 695 | @@ -10574,6 +10579,9 @@ tcp-request inspect-delay <timeout> |
| 696 | Obviously this is unlikely to be very useful and might even be racy, so such |
| 697 | setups are not recommended. |
| 698 | |
| 699 | + Note the inspection delay is shortened if an connection error or shutdown is |
| 700 | + experienced or if the request buffer appears as full. |
| 701 | + |
| 702 | As soon as a rule matches, the request is released and continues as usual. If |
| 703 | the timeout is reached and no rule matches, the default policy will be to let |
| 704 | it pass through unaffected. |
| 705 | @@ -16808,7 +16816,11 @@ path : string |
| 706 | information from databases and keep them in caches. Note that with outgoing |
| 707 | caches, it would be wiser to use "url" instead. With ACLs, it's typically |
| 708 | used to match exact file names (e.g. "/login.php"), or directory parts using |
| 709 | - the derivative forms. See also the "url" and "base" fetch methods. |
| 710 | + the derivative forms. See also the "url" and "base" fetch methods. Please |
| 711 | + note that any fragment reference in the URI ('#' after the path) is strictly |
| 712 | + forbidden by the HTTP standard and will be rejected. However, if the frontend |
| 713 | + receiving the request has "option accept-invalid-http-request", then this |
| 714 | + fragment part will be accepted and will also appear in the path. |
| 715 | |
| 716 | ACL derivatives : |
| 717 | path : exact string match |
| 718 | @@ -16982,7 +16994,11 @@ url : string |
| 719 | "path" is preferred over using "url", because clients may send a full URL as |
| 720 | is normally done with proxies. The only real use is to match "*" which does |
| 721 | not match in "path", and for which there is already a predefined ACL. See |
| 722 | - also "path" and "base". |
| 723 | + also "path" and "base". Please note that any fragment reference in the URI |
| 724 | + ('#' after the path) is strictly forbidden by the HTTP standard and will be |
| 725 | + rejected. However, if the frontend receiving the request has "option |
| 726 | + accept-invalid-http-request", then this fragment part will be accepted and |
| 727 | + will also appear in the url. |
| 728 | |
| 729 | ACL derivatives : |
| 730 | url : exact string match |
| 731 | @@ -17972,6 +17988,13 @@ Timings events in TCP mode: |
| 732 | header (empty line) was never seen, most likely because the server timeout |
| 733 | stroke before the server managed to process the request. |
| 734 | |
| 735 | + - Td: this is the total transfer time of the response payload till the last |
| 736 | + byte sent to the client. In HTTP it starts after the last response header |
| 737 | + (after Tr). |
| 738 | + |
| 739 | + The data sent are not guaranteed to be received by the client, they can be |
| 740 | + stuck in either the kernel or the network. |
| 741 | + |
| 742 | - Ta: total active time for the HTTP request, between the moment the proxy |
| 743 | received the first byte of the request header and the emission of the last |
| 744 | byte of the response body. The exception is when the "logasap" option is |
| 745 | diff --git a/include/common/h2.h b/include/common/h2.h |
| 746 | index 6fbc2db..22acb94 100644 |
| 747 | --- a/include/common/h2.h |
| 748 | +++ b/include/common/h2.h |
| 749 | @@ -201,10 +201,10 @@ extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES]; |
| 750 | |
| 751 | /* various protocol processing functions */ |
| 752 | |
| 753 | -int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len); |
| 754 | +int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len, int relaxed); |
| 755 | int h2_make_h1_trailers(struct http_hdr *list, char *out, int osize); |
| 756 | int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len); |
| 757 | -int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len); |
| 758 | +int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed); |
| 759 | int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len); |
| 760 | int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx); |
| 761 | |
| 762 | diff --git a/include/common/ist.h b/include/common/ist.h |
| 763 | index 0d8b457..9c6c37a 100644 |
| 764 | --- a/include/common/ist.h |
| 765 | +++ b/include/common/ist.h |
| 766 | @@ -204,6 +204,12 @@ static inline size_t istlen(const struct ist ist) |
| 767 | return ist.len; |
| 768 | } |
| 769 | |
| 770 | +/* returns the pointer to the end the string */ |
| 771 | +static inline char *istend(const struct ist ist) |
| 772 | +{ |
| 773 | + return (ist.ptr + ist.len); |
| 774 | +} |
| 775 | + |
| 776 | /* skips to next character in the string, always stops at the end */ |
| 777 | static inline struct ist istnext(const struct ist ist) |
| 778 | { |
| 779 | @@ -647,6 +653,53 @@ static inline const char *ist_find_ctl(const struct ist ist) |
| 780 | return NULL; |
| 781 | } |
| 782 | |
| 783 | +/* Returns a pointer to the first character found <ist> that belongs to the |
| 784 | + * range [min:max] inclusive, or NULL if none is present. The function is |
| 785 | + * optimized for strings having no such chars by processing up to sizeof(long) |
| 786 | + * bytes at once on architectures supporting efficient unaligned accesses. |
| 787 | + * Despite this it is not very fast (~0.43 byte/cycle) and should mostly be |
| 788 | + * used on low match probability when it can save a call to a much slower |
| 789 | + * function. Will not work for characters 0x80 and above. It's optimized for |
| 790 | + * min and max to be known at build time. |
| 791 | + */ |
| 792 | +static inline const char *ist_find_range(const struct ist ist, unsigned char min, unsigned char max) |
| 793 | +{ |
| 794 | + const union { unsigned long v; } __attribute__((packed)) *u; |
| 795 | + const char *curr = (void *)ist.ptr - sizeof(long); |
| 796 | + const char *last = curr + ist.len; |
| 797 | + unsigned long l1, l2; |
| 798 | + |
| 799 | + /* easier with an exclusive boundary */ |
| 800 | + max++; |
| 801 | + |
| 802 | + do { |
| 803 | + curr += sizeof(long); |
| 804 | + if (curr > last) |
| 805 | + break; |
| 806 | + u = (void *)curr; |
| 807 | + /* add 0x<min><min><min><min>..<min> then subtract |
| 808 | + * 0x<max><max><max><max>..<max> to the value to generate a |
| 809 | + * carry in the lower byte if the byte contains a lower value. |
| 810 | + * If we generate a bit 7 that was not there, it means the byte |
| 811 | + * was min..max. |
| 812 | + */ |
| 813 | + l2 = u->v; |
| 814 | + l1 = ~l2 & ((~0UL / 255) * 0x80); /* 0x808080...80 */ |
| 815 | + l2 += (~0UL / 255) * min; /* 0x<min><min>..<min> */ |
| 816 | + l2 -= (~0UL / 255) * max; /* 0x<max><max>..<max> */ |
| 817 | + } while ((l1 & l2) == 0); |
| 818 | + |
| 819 | + last += sizeof(long); |
| 820 | + if (__builtin_expect(curr < last, 0)) { |
| 821 | + do { |
| 822 | + if ((unsigned char)(*curr - min) < (unsigned char)(max - min)) |
| 823 | + return curr; |
| 824 | + curr++; |
| 825 | + } while (curr < last); |
| 826 | + } |
| 827 | + return NULL; |
| 828 | +} |
| 829 | + |
| 830 | /* looks for first occurrence of character <chr> in string <ist> and returns |
| 831 | * the tail of the string starting with this character, or (ist.end,0) if not |
| 832 | * found. |
| 833 | diff --git a/include/common/time.h b/include/common/time.h |
| 834 | index 3b0f640..8e1d6e7 100644 |
| 835 | --- a/include/common/time.h |
| 836 | +++ b/include/common/time.h |
| 837 | @@ -60,7 +60,8 @@ extern THREAD_LOCAL unsigned int samp_time; /* total elapsed time over |
| 838 | extern THREAD_LOCAL unsigned int idle_time; /* total idle time over current sample */ |
| 839 | extern THREAD_LOCAL struct timeval now; /* internal date is a monotonic function of real clock */ |
| 840 | extern THREAD_LOCAL struct timeval date; /* the real current date */ |
| 841 | -extern struct timeval start_date; /* the process's start date */ |
| 842 | +extern struct timeval start_date; /* the process's start date */ |
| 843 | +extern struct timeval ready_date; /* date when the process was considered ready */ |
| 844 | extern THREAD_LOCAL struct timeval before_poll; /* system date before calling poll() */ |
| 845 | extern THREAD_LOCAL struct timeval after_poll; /* system date after leaving poll() */ |
| 846 | extern volatile unsigned long long global_now; |
| 847 | diff --git a/include/types/spoe.h b/include/types/spoe.h |
| 848 | index 56224e1..aef387d 100644 |
| 849 | --- a/include/types/spoe.h |
| 850 | +++ b/include/types/spoe.h |
| 851 | @@ -272,6 +272,7 @@ struct spoe_agent { |
| 852 | struct freq_ctr conn_per_sec; /* connections per second */ |
| 853 | struct freq_ctr err_per_sec; /* connetion errors per second */ |
| 854 | |
| 855 | + unsigned int idles; /* # of idle applets */ |
| 856 | struct eb_root idle_applets; /* idle SPOE applets available to process data */ |
| 857 | struct list applets; /* all SPOE applets for this agent */ |
| 858 | struct list sending_queue; /* Queue of streams waiting to send data */ |
| 859 | diff --git a/reg-tests/http-messaging/h1_to_h1.vtc b/reg-tests/http-messaging/h1_to_h1.vtc |
| 860 | index 7694014..aeb3a60 100644 |
| 861 | --- a/reg-tests/http-messaging/h1_to_h1.vtc |
| 862 | +++ b/reg-tests/http-messaging/h1_to_h1.vtc |
| 863 | @@ -269,3 +269,29 @@ client c3h1 -connect ${h1_feh1_sock} { |
| 864 | # arrive here. |
| 865 | expect_close |
| 866 | } -run |
| 867 | + |
| 868 | +client c4h1 -connect ${h1_feh1_sock} { |
| 869 | + # this request is invalid and advertises an invalid C-L ending with an |
| 870 | + # empty value, which results in a stream error. |
| 871 | + txreq \ |
| 872 | + -req "GET" \ |
| 873 | + -url "/test31.html" \ |
| 874 | + -hdr "content-length: 0," \ |
| 875 | + -hdr "connection: close" |
| 876 | + rxresp |
| 877 | + expect resp.status == 400 |
| 878 | + expect_close |
| 879 | +} -run |
| 880 | + |
| 881 | +client c5h1 -connect ${h1_feh1_sock} { |
| 882 | + # this request is invalid and advertises an empty C-L, which results |
| 883 | + # in a stream error. |
| 884 | + txreq \ |
| 885 | + -req "GET" \ |
| 886 | + -url "/test41.html" \ |
| 887 | + -hdr "content-length:" \ |
| 888 | + -hdr "connection: close" |
| 889 | + rxresp |
| 890 | + expect resp.status == 400 |
| 891 | + expect_close |
| 892 | +} -run |
| 893 | diff --git a/reg-tests/http-messaging/h2_to_h1.vtc b/reg-tests/http-messaging/h2_to_h1.vtc |
| 894 | index 481aded..3ed3751 100644 |
| 895 | --- a/reg-tests/http-messaging/h2_to_h1.vtc |
| 896 | +++ b/reg-tests/http-messaging/h2_to_h1.vtc |
| 897 | @@ -10,6 +10,8 @@ barrier b1 cond 2 -cyclic |
| 898 | barrier b2 cond 2 -cyclic |
| 899 | barrier b3 cond 2 -cyclic |
| 900 | barrier b4 cond 2 -cyclic |
| 901 | +barrier b5 cond 2 -cyclic |
| 902 | +barrier b6 cond 2 -cyclic |
| 903 | |
| 904 | server s1 { |
| 905 | rxreq |
| 906 | @@ -31,6 +33,12 @@ server s1 { |
| 907 | |
| 908 | barrier b4 sync |
| 909 | # the next request is never received |
| 910 | + |
| 911 | + barrier b5 sync |
| 912 | + # the next request is never received |
| 913 | + |
| 914 | + barrier b6 sync |
| 915 | + # the next request is never received |
| 916 | } -repeat 2 -start |
| 917 | |
| 918 | haproxy h1 -conf { |
| 919 | @@ -115,6 +123,32 @@ client c1h2 -connect ${h1_feh2_sock} { |
| 920 | txdata -data "this is sent and ignored" |
| 921 | rxrst |
| 922 | } -run |
| 923 | + |
| 924 | + # fifth request is invalid and advertises an invalid C-L ending with an |
| 925 | + # empty value, which results in a stream error. |
| 926 | + stream 9 { |
| 927 | + barrier b5 sync |
| 928 | + txreq \ |
| 929 | + -req "GET" \ |
| 930 | + -scheme "https" \ |
| 931 | + -url "/test5.html" \ |
| 932 | + -hdr "content-length" "0," \ |
| 933 | + -nostrend |
| 934 | + rxrst |
| 935 | + } -run |
| 936 | + |
| 937 | + # sixth request is invalid and advertises an empty C-L, which results |
| 938 | + # in a stream error. |
| 939 | + stream 11 { |
| 940 | + barrier b6 sync |
| 941 | + txreq \ |
| 942 | + -req "GET" \ |
| 943 | + -scheme "https" \ |
| 944 | + -url "/test6.html" \ |
| 945 | + -hdr "content-length" "" \ |
| 946 | + -nostrend |
| 947 | + rxrst |
| 948 | + } -run |
| 949 | } -run |
| 950 | |
| 951 | # HEAD requests : don't work well yet |
| 952 | @@ -257,4 +291,30 @@ client c3h2 -connect ${h1_feh2_sock} { |
| 953 | txdata -data "this is sent and ignored" |
| 954 | rxrst |
| 955 | } -run |
| 956 | + |
| 957 | + # fifth request is invalid and advertises invalid C-L ending with an |
| 958 | + # empty value, which results in a stream error. |
| 959 | + stream 9 { |
| 960 | + barrier b5 sync |
| 961 | + txreq \ |
| 962 | + -req "POST" \ |
| 963 | + -scheme "https" \ |
| 964 | + -url "/test25.html" \ |
| 965 | + -hdr "content-length" "0," \ |
| 966 | + -nostrend |
| 967 | + rxrst |
| 968 | + } -run |
| 969 | + |
| 970 | + # sixth request is invalid and advertises an empty C-L, which results |
| 971 | + # in a stream error. |
| 972 | + stream 11 { |
| 973 | + barrier b6 sync |
| 974 | + txreq \ |
| 975 | + -req "POST" \ |
| 976 | + -scheme "https" \ |
| 977 | + -url "/test26.html" \ |
| 978 | + -hdr "content-length" "" \ |
| 979 | + -nostrend |
| 980 | + rxrst |
| 981 | + } -run |
| 982 | } -run |
| 983 | diff --git a/reg-tests/http-rules/fragment_in_uri.vtc b/reg-tests/http-rules/fragment_in_uri.vtc |
| 984 | new file mode 100644 |
| 985 | index 0000000..6217513 |
| 986 | --- /dev/null |
| 987 | +++ b/reg-tests/http-rules/fragment_in_uri.vtc |
| 988 | @@ -0,0 +1,35 @@ |
| 989 | +varnishtest "check for fragments in URL" |
| 990 | +#REQUIRE_VERSION=2.0 |
| 991 | + |
| 992 | +# This reg-test checks that '#' is properly blocked in requests |
| 993 | + |
| 994 | +feature ignore_unknown_macro |
| 995 | + |
| 996 | +server s1 { |
| 997 | + rxreq |
| 998 | + txresp -hdr "connection: close" |
| 999 | +} -start |
| 1000 | + |
| 1001 | +haproxy h1 -conf { |
| 1002 | + global |
| 1003 | + |
| 1004 | + defaults |
| 1005 | + mode http |
| 1006 | + timeout connect 1s |
| 1007 | + timeout client 1s |
| 1008 | + timeout server 1s |
| 1009 | + |
| 1010 | + frontend fe_fragment_block |
| 1011 | + bind "fd@${fe_fragment_block}" |
| 1012 | + default_backend be |
| 1013 | + |
| 1014 | + backend be |
| 1015 | + server s1 ${s1_addr}:${s1_port} |
| 1016 | + |
| 1017 | +} -start |
| 1018 | + |
| 1019 | +client c11 -connect ${h1_fe_fragment_block_sock} { |
| 1020 | + txreq -url "/#foo" |
| 1021 | + rxresp |
| 1022 | + expect resp.status == 400 |
| 1023 | +} -run |
| 1024 | diff --git a/reg-tests/http-rules/h1or2_to_h1c.vtc b/reg-tests/http-rules/h1or2_to_h1c.vtc |
| 1025 | index 16e2639..e6b7df4 100644 |
| 1026 | --- a/reg-tests/http-rules/h1or2_to_h1c.vtc |
| 1027 | +++ b/reg-tests/http-rules/h1or2_to_h1c.vtc |
| 1028 | @@ -27,11 +27,11 @@ server s1 { |
| 1029 | -body "This is a body" |
| 1030 | |
| 1031 | expect req.method == "GET" |
| 1032 | - expect req.http.fe-sl1-crc == 992395575 |
| 1033 | - expect req.http.fe-sl2-crc == 1270056220 |
| 1034 | + expect req.http.fe-sl1-crc == 1874847043 |
| 1035 | + expect req.http.fe-sl2-crc == 1142278307 |
| 1036 | expect req.http.fe-hdr-crc == 1719311923 |
| 1037 | - expect req.http.be-sl1-crc == 2604236007 |
| 1038 | - expect req.http.be-sl2-crc == 4181358964 |
| 1039 | + expect req.http.be-sl1-crc == 3455320059 |
| 1040 | + expect req.http.be-sl2-crc == 2509326257 |
| 1041 | expect req.http.be-hdr-crc == 3634102538 |
| 1042 | } -repeat 2 -start |
| 1043 | |
| 1044 | @@ -53,6 +53,7 @@ haproxy h1 -conf { |
| 1045 | http-request set-var(req.path) path |
| 1046 | http-request set-var(req.query) query |
| 1047 | http-request set-var(req.param) url_param(qs_arg) |
| 1048 | + http-request set-var(req.cl) req.fhdr(content-length) |
| 1049 | |
| 1050 | http-request set-header sl1 "sl1: " |
| 1051 | |
| 1052 | @@ -65,8 +66,10 @@ haproxy h1 -conf { |
| 1053 | |
| 1054 | http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;" |
| 1055 | http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>" |
| 1056 | + http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>" |
| 1057 | http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; " |
| 1058 | http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>" |
| 1059 | + http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>" |
| 1060 | http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;" |
| 1061 | http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;" |
| 1062 | http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;" |
| 1063 | @@ -120,6 +123,7 @@ haproxy h1 -conf { |
| 1064 | http-request set-var(req.path) path |
| 1065 | http-request set-var(req.query) query |
| 1066 | http-request set-var(req.param) url_param(qs_arg) |
| 1067 | + http-request set-var(req.cl) req.fhdr(content-length) |
| 1068 | |
| 1069 | http-request set-header sl1 "sl1: " |
| 1070 | |
| 1071 | @@ -132,8 +136,10 @@ haproxy h1 -conf { |
| 1072 | |
| 1073 | http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;" |
| 1074 | http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>" |
| 1075 | + http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>" |
| 1076 | http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; " |
| 1077 | http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>" |
| 1078 | + http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>" |
| 1079 | http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;" |
| 1080 | http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;" |
| 1081 | http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;" |
| 1082 | @@ -171,6 +177,7 @@ client c1h1 -connect ${h1_feh1_sock} { |
| 1083 | txreq \ |
| 1084 | -req GET \ |
| 1085 | -url /path/to/file.extension?qs_arg=qs_value \ |
| 1086 | + -hdr "content-length: 000, 00" \ |
| 1087 | -hdr "hdr1: val1" \ |
| 1088 | -hdr "hdr2: val2a" \ |
| 1089 | -hdr "hdr2: val2b" \ |
| 1090 | @@ -205,6 +212,7 @@ client c1h2 -connect ${h1_feh2_sock} { |
| 1091 | -req GET \ |
| 1092 | -scheme "https" \ |
| 1093 | -url /path/to/file.extension?qs_arg=qs_value \ |
| 1094 | + -hdr "content-length" "000, 00" \ |
| 1095 | -hdr "hdr1" "val1" \ |
| 1096 | -hdr "hdr2" " val2a" \ |
| 1097 | -hdr "hdr2" " val2b" \ |
| 1098 | diff --git a/scripts/publish-release b/scripts/publish-release |
| 1099 | index d785244..f356076 100755 |
| 1100 | --- a/scripts/publish-release |
| 1101 | +++ b/scripts/publish-release |
| 1102 | @@ -22,6 +22,9 @@ NEW= |
| 1103 | DIR= |
| 1104 | DOC=( ) |
| 1105 | |
| 1106 | +# need to have group write on emitted files for others to update |
| 1107 | +umask 002 |
| 1108 | + |
| 1109 | die() { |
| 1110 | [ "$#" -eq 0 ] || echo "$*" >&2 |
| 1111 | exit 1 |
| 1112 | diff --git a/src/checks.c b/src/checks.c |
| 1113 | index 06d6bff..b9f3c9d 100644 |
| 1114 | --- a/src/checks.c |
| 1115 | +++ b/src/checks.c |
| 1116 | @@ -2517,6 +2517,7 @@ static int start_check_task(struct check *check, int mininter, |
| 1117 | { |
| 1118 | struct task *t; |
| 1119 | unsigned long thread_mask = MAX_THREADS_MASK; |
| 1120 | + unsigned long boottime = tv_ms_remain(&start_date, &ready_date); |
| 1121 | |
| 1122 | if (check->type == PR_O2_EXT_CHK) |
| 1123 | thread_mask = 1; |
| 1124 | @@ -2535,11 +2536,19 @@ static int start_check_task(struct check *check, int mininter, |
| 1125 | if (mininter < srv_getinter(check)) |
| 1126 | mininter = srv_getinter(check); |
| 1127 | |
| 1128 | + if (global.spread_checks > 0) { |
| 1129 | + int rnd; |
| 1130 | + |
| 1131 | + rnd = srv_getinter(check) * global.spread_checks / 100; |
| 1132 | + rnd -= (int) (2 * rnd * (ha_random32() / 4294967295.0)); |
| 1133 | + mininter += rnd; |
| 1134 | + } |
| 1135 | + |
| 1136 | if (global.max_spread_checks && mininter > global.max_spread_checks) |
| 1137 | mininter = global.max_spread_checks; |
| 1138 | |
| 1139 | /* check this every ms */ |
| 1140 | - t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck)); |
| 1141 | + t->expire = tick_add(now_ms, MS_TO_TICKS(boottime + mininter * srvpos / nbcheck)); |
| 1142 | check->start = now; |
| 1143 | task_queue(t); |
| 1144 | |
| 1145 | diff --git a/src/chunk.c b/src/chunk.c |
| 1146 | index 100783e..b62e323 100644 |
| 1147 | --- a/src/chunk.c |
| 1148 | +++ b/src/chunk.c |
| 1149 | @@ -153,15 +153,19 @@ int chunk_printf(struct buffer *chk, const char *fmt, ...) |
| 1150 | int chunk_appendf(struct buffer *chk, const char *fmt, ...) |
| 1151 | { |
| 1152 | va_list argp; |
| 1153 | + size_t room; |
| 1154 | int ret; |
| 1155 | |
| 1156 | if (!chk->area || !chk->size) |
| 1157 | return 0; |
| 1158 | |
| 1159 | + room = chk->size - chk->data; |
| 1160 | + if (!room) |
| 1161 | + return chk->data; |
| 1162 | + |
| 1163 | va_start(argp, fmt); |
| 1164 | - ret = vsnprintf(chk->area + chk->data, chk->size - chk->data, fmt, |
| 1165 | - argp); |
| 1166 | - if (ret >= chk->size - chk->data) |
| 1167 | + ret = vsnprintf(chk->area + chk->data, room, fmt, argp); |
| 1168 | + if (ret >= room) |
| 1169 | /* do not copy anything in case of truncation */ |
| 1170 | chk->area[chk->data] = 0; |
| 1171 | else |
| 1172 | diff --git a/src/debug.c b/src/debug.c |
| 1173 | index b35bff5..4303d04 100644 |
| 1174 | --- a/src/debug.c |
| 1175 | +++ b/src/debug.c |
| 1176 | @@ -164,9 +164,10 @@ void ha_task_dump(struct buffer *buf, const struct task *task, const char *pfx) |
| 1177 | if (hlua && hlua->T) { |
| 1178 | chunk_appendf(buf, "stack traceback:\n "); |
| 1179 | append_prefixed_str(buf, hlua_traceback(hlua->T, "\n "), pfx, '\n', 0); |
| 1180 | - b_putchr(buf, '\n'); |
| 1181 | } |
| 1182 | - else |
| 1183 | + |
| 1184 | + /* we may need to terminate the current line */ |
| 1185 | + if (*b_peek(buf, b_data(buf)-1) != '\n') |
| 1186 | b_putchr(buf, '\n'); |
| 1187 | #endif |
| 1188 | } |
| 1189 | diff --git a/src/filters.c b/src/filters.c |
| 1190 | index a7327e8..8027d3e 100644 |
| 1191 | --- a/src/filters.c |
| 1192 | +++ b/src/filters.c |
| 1193 | @@ -292,10 +292,9 @@ flt_init_all() |
| 1194 | int err_code = 0; |
| 1195 | |
| 1196 | for (px = proxies_list; px; px = px->next) { |
| 1197 | - if (px->state == PR_STSTOPPED) { |
| 1198 | - flt_deinit(px); |
| 1199 | + if (px->state == PR_STSTOPPED) |
| 1200 | continue; |
| 1201 | - } |
| 1202 | + |
| 1203 | err_code |= flt_init(px); |
| 1204 | if (err_code & (ERR_ABORT|ERR_FATAL)) { |
| 1205 | ha_alert("Failed to initialize filters for proxy '%s'.\n", |
| 1206 | diff --git a/src/flt_spoe.c b/src/flt_spoe.c |
| 1207 | index 128461b..d329f9f 100644 |
| 1208 | --- a/src/flt_spoe.c |
| 1209 | +++ b/src/flt_spoe.c |
| 1210 | @@ -1276,6 +1276,7 @@ spoe_release_appctx(struct appctx *appctx) |
| 1211 | if (appctx->st0 == SPOE_APPCTX_ST_IDLE) { |
| 1212 | eb32_delete(&spoe_appctx->node); |
| 1213 | _HA_ATOMIC_SUB(&agent->counters.idles, 1); |
| 1214 | + agent->rt[tid].idles--; |
| 1215 | } |
| 1216 | |
| 1217 | appctx->st0 = SPOE_APPCTX_ST_END; |
| 1218 | @@ -1489,6 +1490,7 @@ spoe_handle_connecting_appctx(struct appctx *appctx) |
| 1219 | |
| 1220 | default: |
| 1221 | _HA_ATOMIC_ADD(&agent->counters.idles, 1); |
| 1222 | + agent->rt[tid].idles++; |
| 1223 | appctx->st0 = SPOE_APPCTX_ST_IDLE; |
| 1224 | SPOE_APPCTX(appctx)->node.key = 0; |
| 1225 | eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node); |
| 1226 | @@ -1731,12 +1733,6 @@ spoe_handle_processing_appctx(struct appctx *appctx) |
| 1227 | (agent->b.be->nbpend || |
| 1228 | (srv && (srv->nbpend || (srv->maxconn && srv->served >= srv_dynamic_maxconn(srv)))))); |
| 1229 | |
| 1230 | - /* Don"t try to send new frame we are waiting for at lease a ack, in |
| 1231 | - * sync mode or if applet must be closed ASAP |
| 1232 | - */ |
| 1233 | - if (appctx->st0 == SPOE_APPCTX_ST_WAITING_SYNC_ACK || (close_asap && SPOE_APPCTX(appctx)->cur_fpa)) |
| 1234 | - skip_sending = 1; |
| 1235 | - |
| 1236 | /* receiving_frame loop */ |
| 1237 | while (!skip_receiving) { |
| 1238 | ret = spoe_handle_receiving_frame_appctx(appctx, &skip_receiving); |
| 1239 | @@ -1757,6 +1753,12 @@ spoe_handle_processing_appctx(struct appctx *appctx) |
| 1240 | } |
| 1241 | } |
| 1242 | |
| 1243 | + /* Don"t try to send new frame we are waiting for at lease a ack, in |
| 1244 | + * sync mode or if applet must be closed ASAP |
| 1245 | + */ |
| 1246 | + if (appctx->st0 == SPOE_APPCTX_ST_WAITING_SYNC_ACK || (close_asap && SPOE_APPCTX(appctx)->cur_fpa)) |
| 1247 | + skip_sending = 1; |
| 1248 | + |
| 1249 | /* send_frame loop */ |
| 1250 | while (!skip_sending && SPOE_APPCTX(appctx)->cur_fpa < agent->max_fpa) { |
| 1251 | ret = spoe_handle_sending_frame_appctx(appctx, &skip_sending); |
| 1252 | @@ -1803,6 +1805,7 @@ spoe_handle_processing_appctx(struct appctx *appctx) |
| 1253 | goto next; |
| 1254 | } |
| 1255 | _HA_ATOMIC_ADD(&agent->counters.idles, 1); |
| 1256 | + agent->rt[tid].idles++; |
| 1257 | appctx->st0 = SPOE_APPCTX_ST_IDLE; |
| 1258 | eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node); |
| 1259 | } |
| 1260 | @@ -1968,6 +1971,7 @@ spoe_handle_appctx(struct appctx *appctx) |
| 1261 | |
| 1262 | case SPOE_APPCTX_ST_IDLE: |
| 1263 | _HA_ATOMIC_SUB(&agent->counters.idles, 1); |
| 1264 | + agent->rt[tid].idles--; |
| 1265 | eb32_delete(&SPOE_APPCTX(appctx)->node); |
| 1266 | if (stopping && |
| 1267 | LIST_ISEMPTY(&agent->rt[tid].sending_queue) && |
| 1268 | @@ -2110,8 +2114,8 @@ spoe_queue_context(struct spoe_context *ctx) |
| 1269 | struct spoe_appctx *spoe_appctx; |
| 1270 | |
| 1271 | /* Check if we need to create a new SPOE applet or not. */ |
| 1272 | - if (!eb_is_empty(&agent->rt[tid].idle_applets) && |
| 1273 | - (agent->rt[tid].processing == 1 || agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec))) |
| 1274 | + if (agent->rt[tid].processing < agent->rt[tid].idles || |
| 1275 | + agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec)) |
| 1276 | goto end; |
| 1277 | |
| 1278 | SPOE_PRINTF(stderr, "%d.%06d [SPOE/%-15s] %s: stream=%p" |
| 1279 | @@ -3038,6 +3042,14 @@ spoe_sig_stop(struct sig_handler *sh) |
| 1280 | while (p) { |
| 1281 | struct flt_conf *fconf; |
| 1282 | |
| 1283 | + /* SPOE filter are not initialized for disabled proxoes. Move to |
| 1284 | + * the next one |
| 1285 | + */ |
| 1286 | + if (p->state == PR_STSTOPPED) { |
| 1287 | + p = p->next; |
| 1288 | + continue; |
| 1289 | + } |
| 1290 | + |
| 1291 | list_for_each_entry(fconf, &p->filter_configs, list) { |
| 1292 | struct spoe_config *conf; |
| 1293 | struct spoe_agent *agent; |
| 1294 | @@ -3079,7 +3091,6 @@ spoe_init(struct proxy *px, struct flt_conf *fconf) |
| 1295 | conf->agent_fe.accept = frontend_accept; |
| 1296 | conf->agent_fe.srv = NULL; |
| 1297 | conf->agent_fe.timeout.client = TICK_ETERNITY; |
| 1298 | - conf->agent_fe.default_target = &spoe_applet.obj_type; |
| 1299 | conf->agent_fe.fe_req_ana = AN_REQ_SWITCHING_RULES; |
| 1300 | |
| 1301 | if (!sighandler_registered) { |
| 1302 | @@ -3170,6 +3181,7 @@ spoe_check(struct proxy *px, struct flt_conf *fconf) |
| 1303 | conf->agent->rt[i].engine_id = NULL; |
| 1304 | conf->agent->rt[i].frame_size = conf->agent->max_frame_size; |
| 1305 | conf->agent->rt[i].processing = 0; |
| 1306 | + conf->agent->rt[i].idles = 0; |
| 1307 | LIST_INIT(&conf->agent->rt[i].applets); |
| 1308 | LIST_INIT(&conf->agent->rt[i].sending_queue); |
| 1309 | LIST_INIT(&conf->agent->rt[i].waiting_queue); |
| 1310 | diff --git a/src/h1.c b/src/h1.c |
| 1311 | index 69a7ced..30c49ea 100644 |
| 1312 | --- a/src/h1.c |
| 1313 | +++ b/src/h1.c |
| 1314 | @@ -32,13 +32,20 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) |
| 1315 | int not_first = !!(h1m->flags & H1_MF_CLEN); |
| 1316 | struct ist word; |
| 1317 | |
| 1318 | - word.ptr = value->ptr - 1; // -1 for next loop's pre-increment |
| 1319 | + word.ptr = value->ptr; |
| 1320 | e = value->ptr + value->len; |
| 1321 | |
| 1322 | - while (++word.ptr < e) { |
| 1323 | - /* skip leading delimitor and blanks */ |
| 1324 | - if (unlikely(HTTP_IS_LWS(*word.ptr))) |
| 1325 | + while (1) { |
| 1326 | + if (word.ptr >= e) { |
| 1327 | + /* empty header or empty value */ |
| 1328 | + goto fail; |
| 1329 | + } |
| 1330 | + |
| 1331 | + /* skip leading delimiter and blanks */ |
| 1332 | + if (unlikely(HTTP_IS_LWS(*word.ptr))) { |
| 1333 | + word.ptr++; |
| 1334 | continue; |
| 1335 | + } |
| 1336 | |
| 1337 | /* digits only now */ |
| 1338 | for (cl = 0, n = word.ptr; n < e; n++) { |
| 1339 | @@ -49,6 +56,14 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) |
| 1340 | goto fail; |
| 1341 | break; |
| 1342 | } |
| 1343 | + |
| 1344 | + if (unlikely(!cl && n > word.ptr)) { |
| 1345 | + /* There was a leading zero before this digit, |
| 1346 | + * let's trim it. |
| 1347 | + */ |
| 1348 | + word.ptr = n; |
| 1349 | + } |
| 1350 | + |
| 1351 | if (unlikely(cl > ULLONG_MAX / 10ULL)) |
| 1352 | goto fail; /* multiply overflow */ |
| 1353 | cl = cl * 10ULL; |
| 1354 | @@ -77,6 +92,13 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) |
| 1355 | h1m->flags |= H1_MF_CLEN; |
| 1356 | h1m->curr_len = h1m->body_len = cl; |
| 1357 | *value = word; |
| 1358 | + |
| 1359 | + /* Now either n==e and we're done, or n points to the comma, |
| 1360 | + * and we skip it and continue. |
| 1361 | + */ |
| 1362 | + if (n++ == e) |
| 1363 | + break; |
| 1364 | + |
| 1365 | word.ptr = n; |
| 1366 | } |
| 1367 | /* here we've reached the end with a single value or a series of |
| 1368 | @@ -390,11 +412,11 @@ int h1_headers_to_hdr_list(char *start, const char *stop, |
| 1369 | defined(__ARM_ARCH_7A__) |
| 1370 | /* speedup: skip bytes not between 0x21 and 0x7e inclusive */ |
| 1371 | while (ptr <= end - sizeof(int)) { |
| 1372 | - int x = *(int *)ptr - 0x21212121; |
| 1373 | + int x = *(int *)ptr - 0x24242424; |
| 1374 | if (x & 0x80808080) |
| 1375 | break; |
| 1376 | |
| 1377 | - x -= 0x5e5e5e5e; |
| 1378 | + x -= 0x5b5b5b5b; |
| 1379 | if (!(x & 0x80808080)) |
| 1380 | break; |
| 1381 | |
| 1382 | @@ -406,8 +428,15 @@ int h1_headers_to_hdr_list(char *start, const char *stop, |
| 1383 | goto http_msg_ood; |
| 1384 | } |
| 1385 | http_msg_rquri2: |
| 1386 | - if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 included */ |
| 1387 | + if (likely((unsigned char)(*ptr - 33) <= 93)) { /* 33 to 126 included */ |
| 1388 | + if (*ptr == '#') { |
| 1389 | + if (h1m->err_pos < -1) /* PR_O2_REQBUG_OK not set */ |
| 1390 | + goto invalid_char; |
| 1391 | + if (h1m->err_pos == -1) /* PR_O2_REQBUG_OK set: just log */ |
| 1392 | + h1m->err_pos = ptr - start + skip; |
| 1393 | + } |
| 1394 | EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rquri2, http_msg_ood, state, H1_MSG_RQURI); |
| 1395 | + } |
| 1396 | |
| 1397 | if (likely(HTTP_IS_SPHT(*ptr))) { |
| 1398 | sl.rq.u.len = ptr - sl.rq.u.ptr; |
| 1399 | diff --git a/src/h2.c b/src/h2.c |
| 1400 | index bfc0bda..a36b93b 100644 |
| 1401 | --- a/src/h2.c |
| 1402 | +++ b/src/h2.c |
| 1403 | @@ -61,6 +61,26 @@ static int has_forbidden_char(const struct ist ist, const char *start) |
| 1404 | return 0; |
| 1405 | } |
| 1406 | |
| 1407 | +/* Looks into <ist> for forbidden characters for :path values (0x00..0x1F, |
| 1408 | + * 0x20, 0x23), starting at pointer <start> which must be within <ist>. |
| 1409 | + * Returns non-zero if such a character is found, 0 otherwise. When run on |
| 1410 | + * unlikely header match, it's recommended to first check for the presence |
| 1411 | + * of control chars using ist_find_ctl(). |
| 1412 | + */ |
| 1413 | +static inline int http_path_has_forbidden_char(const struct ist ist, const char *start) |
| 1414 | +{ |
| 1415 | + do { |
| 1416 | + if ((uint8_t)*start <= 0x23) { |
| 1417 | + if ((uint8_t)*start < 0x20) |
| 1418 | + return 1; |
| 1419 | + if ((1U << ((uint8_t)*start & 0x1F)) & ((1<<3) | (1<<0))) |
| 1420 | + return 1; |
| 1421 | + } |
| 1422 | + start++; |
| 1423 | + } while (start < istend(ist)); |
| 1424 | + return 0; |
| 1425 | +} |
| 1426 | + |
| 1427 | /* Prepare the request line into <*ptr> (stopping at <end>) from pseudo headers |
| 1428 | * stored in <phdr[]>. <fields> indicates what was found so far. This should be |
| 1429 | * called once at the detection of the first general header field or at the end |
| 1430 | @@ -158,8 +178,12 @@ static int h2_prepare_h1_reqline(uint32_t fields, struct ist *phdr, char **ptr, |
| 1431 | * |
| 1432 | * The Cookie header will be reassembled at the end, and for this, the <list> |
| 1433 | * will be used to create a linked list, so its contents may be destroyed. |
| 1434 | + * |
| 1435 | + * When <relaxed> is non-nul, some non-dangerous checks will be ignored. This |
| 1436 | + * is in order to satisfy "option accept-invalid-http-request" for |
| 1437 | + * interoperability purposes. |
| 1438 | */ |
| 1439 | -int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len) |
| 1440 | +int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len, int relaxed) |
| 1441 | { |
| 1442 | struct ist phdr_val[H2_PHDR_NUM_ENTRIES]; |
| 1443 | char *out_end = out + osize; |
| 1444 | @@ -196,9 +220,15 @@ int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int |
| 1445 | /* RFC7540#10.3: intermediaries forwarding to HTTP/1 must take care of |
| 1446 | * rejecting NUL, CR and LF characters. |
| 1447 | */ |
| 1448 | - ctl = ist_find_ctl(list[idx].v); |
| 1449 | - if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) |
| 1450 | - goto fail; |
| 1451 | + if (phdr == H2_PHDR_IDX_PATH && !relaxed) { |
| 1452 | + ctl = ist_find_range(list[idx].v, 0, '#'); |
| 1453 | + if (unlikely(ctl) && http_path_has_forbidden_char(list[idx].v, ctl)) |
| 1454 | + goto fail; |
| 1455 | + } else { |
| 1456 | + ctl = ist_find_ctl(list[idx].v); |
| 1457 | + if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) |
| 1458 | + goto fail; |
| 1459 | + } |
| 1460 | |
| 1461 | if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { |
| 1462 | /* insert a pseudo header by its index (in phdr) and value (in value) */ |
| 1463 | @@ -458,13 +488,20 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon |
| 1464 | int not_first = !!(*msgf & H2_MSGF_BODY_CL); |
| 1465 | struct ist word; |
| 1466 | |
| 1467 | - word.ptr = value->ptr - 1; // -1 for next loop's pre-increment |
| 1468 | + word.ptr = value->ptr; |
| 1469 | e = value->ptr + value->len; |
| 1470 | |
| 1471 | - while (++word.ptr < e) { |
| 1472 | - /* skip leading delimitor and blanks */ |
| 1473 | - if (unlikely(HTTP_IS_LWS(*word.ptr))) |
| 1474 | + while (1) { |
| 1475 | + if (word.ptr >= e) { |
| 1476 | + /* empty header or empty value */ |
| 1477 | + goto fail; |
| 1478 | + } |
| 1479 | + |
| 1480 | + /* skip leading delimiter and blanks */ |
| 1481 | + if (unlikely(HTTP_IS_LWS(*word.ptr))) { |
| 1482 | + word.ptr++; |
| 1483 | continue; |
| 1484 | + } |
| 1485 | |
| 1486 | /* digits only now */ |
| 1487 | for (cl = 0, n = word.ptr; n < e; n++) { |
| 1488 | @@ -475,6 +512,14 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon |
| 1489 | goto fail; |
| 1490 | break; |
| 1491 | } |
| 1492 | + |
| 1493 | + if (unlikely(!cl && n > word.ptr)) { |
| 1494 | + /* There was a leading zero before this digit, |
| 1495 | + * let's trim it. |
| 1496 | + */ |
| 1497 | + word.ptr = n; |
| 1498 | + } |
| 1499 | + |
| 1500 | if (unlikely(cl > ULLONG_MAX / 10ULL)) |
| 1501 | goto fail; /* multiply overflow */ |
| 1502 | cl = cl * 10ULL; |
| 1503 | @@ -503,6 +548,13 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon |
| 1504 | *msgf |= H2_MSGF_BODY_CL; |
| 1505 | *body_len = cl; |
| 1506 | *value = word; |
| 1507 | + |
| 1508 | + /* Now either n==e and we're done, or n points to the comma, |
| 1509 | + * and we skip it and continue. |
| 1510 | + */ |
| 1511 | + if (n++ == e) |
| 1512 | + break; |
| 1513 | + |
| 1514 | word.ptr = n; |
| 1515 | } |
| 1516 | /* here we've reached the end with a single value or a series of |
| 1517 | @@ -626,8 +678,12 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, |
| 1518 | * |
| 1519 | * The Cookie header will be reassembled at the end, and for this, the <list> |
| 1520 | * will be used to create a linked list, so its contents may be destroyed. |
| 1521 | + * |
| 1522 | + * When <relaxed> is non-nul, some non-dangerous checks will be ignored. This |
| 1523 | + * is in order to satisfy "option accept-invalid-http-request" for |
| 1524 | + * interoperability purposes. |
| 1525 | */ |
| 1526 | -int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len) |
| 1527 | +int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed) |
| 1528 | { |
| 1529 | struct ist phdr_val[H2_PHDR_NUM_ENTRIES]; |
| 1530 | uint32_t fields; /* bit mask of H2_PHDR_FND_* */ |
| 1531 | @@ -664,11 +720,18 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms |
| 1532 | } |
| 1533 | |
| 1534 | /* RFC7540#10.3: intermediaries forwarding to HTTP/1 must take care of |
| 1535 | - * rejecting NUL, CR and LF characters. |
| 1536 | + * rejecting NUL, CR and LF characters. For :path we reject all CTL |
| 1537 | + * chars, spaces, and '#'. |
| 1538 | */ |
| 1539 | - ctl = ist_find_ctl(list[idx].v); |
| 1540 | - if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) |
| 1541 | - goto fail; |
| 1542 | + if (phdr == H2_PHDR_IDX_PATH && !relaxed) { |
| 1543 | + ctl = ist_find_range(list[idx].v, 0, '#'); |
| 1544 | + if (unlikely(ctl) && http_path_has_forbidden_char(list[idx].v, ctl)) |
| 1545 | + goto fail; |
| 1546 | + } else { |
| 1547 | + ctl = ist_find_ctl(list[idx].v); |
| 1548 | + if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) |
| 1549 | + goto fail; |
| 1550 | + } |
| 1551 | |
| 1552 | if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { |
| 1553 | /* insert a pseudo header by its index (in phdr) and value (in value) */ |
| 1554 | diff --git a/src/haproxy.c b/src/haproxy.c |
| 1555 | index f6166d4..b22e954 100644 |
| 1556 | --- a/src/haproxy.c |
| 1557 | +++ b/src/haproxy.c |
| 1558 | @@ -1994,7 +1994,18 @@ static void init(int argc, char **argv) |
| 1559 | } |
| 1560 | } |
| 1561 | |
| 1562 | + /* update the ready date that will be used to count the startup time |
| 1563 | + * during config checks (e.g. to schedule certain tasks if needed) |
| 1564 | + */ |
| 1565 | + gettimeofday(&date, NULL); |
| 1566 | + ready_date = date; |
| 1567 | + |
| 1568 | err_code |= check_config_validity(); |
| 1569 | + |
| 1570 | + /* update the ready date to also account for the check time */ |
| 1571 | + gettimeofday(&date, NULL); |
| 1572 | + ready_date = date; |
| 1573 | + |
| 1574 | if (err_code & (ERR_ABORT|ERR_FATAL)) { |
| 1575 | ha_alert("Fatal errors found in configuration.\n"); |
| 1576 | exit(1); |
| 1577 | @@ -2565,6 +2576,7 @@ void deinit(void) |
| 1578 | free(rdr->cond); |
| 1579 | } |
| 1580 | free(rdr->rdr_str); |
| 1581 | + free(rdr->cookie_str); |
| 1582 | list_for_each_entry_safe(lf, lfb, &rdr->rdr_fmt, list) { |
| 1583 | LIST_DEL(&lf->list); |
| 1584 | free(lf); |
| 1585 | @@ -2664,6 +2676,7 @@ void deinit(void) |
| 1586 | LIST_DEL(&l->by_bind); |
| 1587 | free(l->name); |
| 1588 | free(l->counters); |
| 1589 | + free(l->interface); |
| 1590 | free(l); |
| 1591 | } |
| 1592 | |
| 1593 | @@ -3258,6 +3271,10 @@ int main(int argc, char **argv) |
| 1594 | argv[0], (int)limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock); |
| 1595 | } |
| 1596 | |
| 1597 | + /* update the ready date a last time to also account for final setup time */ |
| 1598 | + gettimeofday(&date, NULL); |
| 1599 | + ready_date = date; |
| 1600 | + |
| 1601 | if (global.mode & (MODE_DAEMON | MODE_MWORKER | MODE_MWORKER_WAIT)) { |
| 1602 | struct proxy *px; |
| 1603 | struct peers *curpeers; |
| 1604 | @@ -3323,7 +3340,7 @@ int main(int argc, char **argv) |
| 1605 | list_for_each_entry(child, &proc_list, list) { |
| 1606 | if (child->relative_pid == relative_pid && |
| 1607 | child->reloads == 0 && child->options & PROC_O_TYPE_WORKER) { |
| 1608 | - child->timestamp = now.tv_sec; |
| 1609 | + child->timestamp = date.tv_sec; |
| 1610 | child->pid = ret; |
| 1611 | child->version = strdup(haproxy_version); |
| 1612 | break; |
| 1613 | diff --git a/src/hlua.c b/src/hlua.c |
| 1614 | index bd8a679..b97442d 100644 |
| 1615 | --- a/src/hlua.c |
| 1616 | +++ b/src/hlua.c |
| 1617 | @@ -3622,7 +3622,9 @@ __LJMP static int hlua_applet_tcp_set_var(lua_State *L) |
| 1618 | memset(&smp, 0, sizeof(smp)); |
| 1619 | hlua_lua2smp(L, 3, &smp); |
| 1620 | |
| 1621 | - /* Store the sample in a variable. */ |
| 1622 | + /* Store the sample in a variable. We don't need to dup the smp, vars API |
| 1623 | + * already takes care of duplicating dynamic var data. |
| 1624 | + */ |
| 1625 | smp_set_owner(&smp, s->be, s->sess, s, 0); |
| 1626 | vars_set_by_name(name, len, &smp); |
| 1627 | return 0; |
| 1628 | @@ -4158,7 +4160,9 @@ __LJMP static int hlua_applet_http_set_var(lua_State *L) |
| 1629 | memset(&smp, 0, sizeof(smp)); |
| 1630 | hlua_lua2smp(L, 3, &smp); |
| 1631 | |
| 1632 | - /* Store the sample in a variable. */ |
| 1633 | + /* Store the sample in a variable. We don't need to dup the smp, vars API |
| 1634 | + * already takes care of duplicating dynamic var data. |
| 1635 | + */ |
| 1636 | smp_set_owner(&smp, s->be, s->sess, s, 0); |
| 1637 | vars_set_by_name(name, len, &smp); |
| 1638 | return 0; |
| 1639 | @@ -5768,7 +5772,9 @@ __LJMP static int hlua_set_var(lua_State *L) |
| 1640 | memset(&smp, 0, sizeof(smp)); |
| 1641 | hlua_lua2smp(L, 3, &smp); |
| 1642 | |
| 1643 | - /* Store the sample in a variable. */ |
| 1644 | + /* Store the sample in a variable. We don't need to dup the smp, vars API |
| 1645 | + * already takes care of duplicating dynamic var data. |
| 1646 | + */ |
| 1647 | smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); |
| 1648 | vars_set_by_name(name, len, &smp); |
| 1649 | return 0; |
| 1650 | @@ -6507,6 +6513,10 @@ static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp, |
| 1651 | |
| 1652 | /* Convert the returned value in sample. */ |
| 1653 | hlua_lua2smp(stream->hlua->T, -1, smp); |
| 1654 | + /* dup the smp before popping the related lua value and |
| 1655 | + * returning it to haproxy |
| 1656 | + */ |
| 1657 | + smp_dup(smp); |
| 1658 | lua_pop(stream->hlua->T, 1); |
| 1659 | return 1; |
| 1660 | |
| 1661 | @@ -6655,6 +6665,10 @@ static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp |
| 1662 | |
| 1663 | /* Convert the returned value in sample. */ |
| 1664 | hlua_lua2smp(stream->hlua->T, -1, smp); |
| 1665 | + /* dup the smp before popping the related lua value and |
| 1666 | + * returning it to haproxy |
| 1667 | + */ |
| 1668 | + smp_dup(smp); |
| 1669 | lua_pop(stream->hlua->T, 1); |
| 1670 | |
| 1671 | /* Set the end of execution flag. */ |
| 1672 | diff --git a/src/http.c b/src/http.c |
| 1673 | index 1ee891c..74833a8 100644 |
| 1674 | --- a/src/http.c |
| 1675 | +++ b/src/http.c |
| 1676 | @@ -449,7 +449,7 @@ const char *http_get_reason(unsigned int status) |
| 1677 | case 226: return "IM Used"; |
| 1678 | case 300: return "Multiple Choices"; |
| 1679 | case 301: return "Moved Permanently"; |
| 1680 | - case 302: return "Moved Temporarily"; |
| 1681 | + case 302: return "Found"; |
| 1682 | case 303: return "See Other"; |
| 1683 | case 304: return "Not Modified"; |
| 1684 | case 305: return "Use Proxy"; |
| 1685 | diff --git a/src/http_msg.c b/src/http_msg.c |
| 1686 | index e07c81c..c38a0f0 100644 |
| 1687 | --- a/src/http_msg.c |
| 1688 | +++ b/src/http_msg.c |
| 1689 | @@ -716,11 +716,11 @@ const char *http_parse_reqline(struct http_msg *msg, |
| 1690 | defined(__ARM_ARCH_7A__) |
| 1691 | /* speedup: skip bytes not between 0x21 and 0x7e inclusive */ |
| 1692 | while (ptr <= end - sizeof(int)) { |
| 1693 | - int x = *(int *)ptr - 0x21212121; |
| 1694 | + int x = *(int *)ptr - 0x24242424; |
| 1695 | if (x & 0x80808080) |
| 1696 | break; |
| 1697 | |
| 1698 | - x -= 0x5e5e5e5e; |
| 1699 | + x -= 0x5b5b5b5b; |
| 1700 | if (!(x & 0x80808080)) |
| 1701 | break; |
| 1702 | |
| 1703 | @@ -732,8 +732,15 @@ const char *http_parse_reqline(struct http_msg *msg, |
| 1704 | goto http_msg_ood; |
| 1705 | } |
| 1706 | http_msg_rquri2: |
| 1707 | - if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 included */ |
| 1708 | + if (likely((unsigned char)(*ptr - 33) <= 93)) { /* 33 to 126 included */ |
| 1709 | + if (*ptr == '#') { |
| 1710 | + if (msg->err_pos < -1) /* PR_O2_REQBUG_OK not set */ |
| 1711 | + goto invalid_char; |
| 1712 | + if (msg->err_pos == -1) /* PR_O2_REQBUG_OK set: just log */ |
| 1713 | + msg->err_pos = ptr - msg_start; |
| 1714 | + } |
| 1715 | EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rquri2, http_msg_ood, state, HTTP_MSG_RQURI); |
| 1716 | + } |
| 1717 | |
| 1718 | if (likely(HTTP_IS_SPHT(*ptr))) { |
| 1719 | msg->sl.rq.u_l = ptr - msg_start - msg->sl.rq.u; |
| 1720 | diff --git a/src/log.c b/src/log.c |
| 1721 | index a63ba61..708b4db 100644 |
| 1722 | --- a/src/log.c |
| 1723 | +++ b/src/log.c |
| 1724 | @@ -834,6 +834,10 @@ int parse_logsrv(char **args, struct list *logsrvs, int do_del, char **err) |
| 1725 | } |
| 1726 | |
| 1727 | node = malloc(sizeof(*node)); |
| 1728 | + if (!node) { |
| 1729 | + memprintf(err, "out of memory error"); |
| 1730 | + goto error; |
| 1731 | + } |
| 1732 | memcpy(node, logsrv, sizeof(struct logsrv)); |
| 1733 | node->ref = logsrv; |
| 1734 | LIST_INIT(&node->list); |
| 1735 | diff --git a/src/mux_h1.c b/src/mux_h1.c |
| 1736 | index 4c91672..426e996 100644 |
| 1737 | --- a/src/mux_h1.c |
| 1738 | +++ b/src/mux_h1.c |
| 1739 | @@ -553,8 +553,10 @@ static void h1_release(struct h1c *h1c) |
| 1740 | h1c->task = NULL; |
| 1741 | } |
| 1742 | |
| 1743 | - if (h1c->wait_event.tasklet) |
| 1744 | + if (h1c->wait_event.tasklet) { |
| 1745 | tasklet_free(h1c->wait_event.tasklet); |
| 1746 | + h1c->wait_event.tasklet = NULL; |
| 1747 | + } |
| 1748 | |
| 1749 | h1s_destroy(h1c->h1s); |
| 1750 | if (conn) { |
| 1751 | @@ -2210,7 +2212,8 @@ static int h1_process(struct h1c * h1c) |
| 1752 | if (!h1s_data_pending(h1s) && h1s && h1s->cs && h1s->cs->data_cb->wake && |
| 1753 | (h1s->flags & H1S_F_REOS || h1c->flags & H1C_F_CS_ERROR || |
| 1754 | conn->flags & (CO_FL_ERROR | CO_FL_SOCK_WR_SH))) { |
| 1755 | - if (h1c->flags & H1C_F_CS_ERROR || ((conn->flags & CO_FL_ERROR) && !b_data(&h1c->ibuf))) |
| 1756 | + if (h1c->flags & H1C_F_CS_ERROR || ((conn->flags & CO_FL_ERROR) && |
| 1757 | + ((h1s->cs->flags & (CS_FL_EOI|CS_FL_EOS)) || !b_data(&h1c->ibuf)))) |
| 1758 | h1s->cs->flags |= CS_FL_ERROR; |
| 1759 | h1s->cs->data_cb->wake(h1s->cs); |
| 1760 | } |
| 1761 | @@ -2496,6 +2499,9 @@ static void h1_shutw_conn(struct connection *conn) |
| 1762 | conn_xprt_shutw(conn); |
| 1763 | conn_sock_shutw(conn, (h1c && !(h1c->flags & H1C_F_ST_SILENT_SHUT))); |
| 1764 | h1c->flags = (h1c->flags & ~H1C_F_CS_SHUTW_NOW) | H1C_F_CS_SHUTDOWN; |
| 1765 | + |
| 1766 | + if (h1c->wait_event.tasklet && !h1c->wait_event.events) |
| 1767 | + tasklet_wakeup(h1c->wait_event.tasklet); |
| 1768 | } |
| 1769 | |
| 1770 | /* Called from the upper layer, to unsubscribe to events */ |
| 1771 | @@ -2600,15 +2606,21 @@ static size_t h1_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun |
| 1772 | |
| 1773 | if (!(h1c->flags & (H1C_F_OUT_FULL|H1C_F_OUT_ALLOC))) |
| 1774 | ret = h1_process_output(h1c, buf, count); |
| 1775 | + |
| 1776 | if (!ret) |
| 1777 | break; |
| 1778 | total += ret; |
| 1779 | count -= ret; |
| 1780 | + |
| 1781 | if ((h1c->wait_event.events & SUB_RETRY_SEND) || !h1_send(h1c)) |
| 1782 | break; |
| 1783 | + |
| 1784 | + if ((h1c->conn->flags & (CO_FL_ERROR|CO_FL_SOCK_WR_SH))) |
| 1785 | + break; |
| 1786 | } |
| 1787 | |
| 1788 | - if (h1c->flags & H1C_F_CS_ERROR) |
| 1789 | + if (h1c->flags & H1C_F_CS_ERROR || ((h1c->conn->flags & CO_FL_ERROR) && |
| 1790 | + ((cs->flags & (CS_FL_EOI|CS_FL_EOS)) || !b_data(&h1c->ibuf)))) |
| 1791 | cs->flags |= CS_FL_ERROR; |
| 1792 | |
| 1793 | h1_refresh_timeout(h1c); |
| 1794 | diff --git a/src/mux_h2.c b/src/mux_h2.c |
| 1795 | index 3141b36..6aaaa3a 100644 |
| 1796 | --- a/src/mux_h2.c |
| 1797 | +++ b/src/mux_h2.c |
| 1798 | @@ -608,6 +608,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s |
| 1799 | |
| 1800 | h2c->proxy = prx; |
| 1801 | h2c->task = NULL; |
| 1802 | + h2c->wait_event.tasklet = NULL; |
| 1803 | h2c->idle_start = now_ms; |
| 1804 | if (tick_isset(h2c->timeout)) { |
| 1805 | t = task_new(tid_bit); |
| 1806 | @@ -2117,8 +2118,10 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) |
| 1807 | if (h2s->st != H2_SS_CLOSED) { |
| 1808 | error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len); |
| 1809 | /* unrecoverable error ? */ |
| 1810 | - if (h2c->st0 >= H2_CS_ERROR) |
| 1811 | + if (h2c->st0 >= H2_CS_ERROR) { |
| 1812 | + sess_log(h2c->conn->owner); |
| 1813 | goto out; |
| 1814 | + } |
| 1815 | |
| 1816 | if (error == 0) { |
| 1817 | /* Demux not blocked because of the stream, it is an incomplete frame */ |
| 1818 | @@ -2131,6 +2134,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) |
| 1819 | /* Failed to decode this frame (e.g. too large request) |
| 1820 | * but the HPACK decompressor is still synchronized. |
| 1821 | */ |
| 1822 | + sess_log(h2c->conn->owner); |
| 1823 | h2s_error(h2s, H2_ERR_INTERNAL_ERROR); |
| 1824 | h2c->st0 = H2_CS_FRAME_E; |
| 1825 | goto out; |
| 1826 | @@ -2141,6 +2145,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) |
| 1827 | * the data and send another RST. |
| 1828 | */ |
| 1829 | error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len); |
| 1830 | + sess_log(h2c->conn->owner); |
| 1831 | h2s = (struct h2s*)h2_error_stream; |
| 1832 | goto send_rst; |
| 1833 | } |
| 1834 | @@ -2156,8 +2161,10 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) |
| 1835 | error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len); |
| 1836 | |
| 1837 | /* unrecoverable error ? */ |
| 1838 | - if (h2c->st0 >= H2_CS_ERROR) |
| 1839 | + if (h2c->st0 >= H2_CS_ERROR) { |
| 1840 | + sess_log(h2c->conn->owner); |
| 1841 | goto out; |
| 1842 | + } |
| 1843 | |
| 1844 | if (error <= 0) { |
| 1845 | if (error == 0) { |
| 1846 | @@ -2170,6 +2177,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) |
| 1847 | /* Failed to decode this stream (e.g. too large request) |
| 1848 | * but the HPACK decompressor is still synchronized. |
| 1849 | */ |
| 1850 | + sess_log(h2c->conn->owner); |
| 1851 | h2s = (struct h2s*)h2_error_stream; |
| 1852 | goto send_rst; |
| 1853 | } |
| 1854 | @@ -3436,7 +3444,7 @@ static void h2_detach(struct conn_stream *cs) |
| 1855 | /* refresh the timeout if none was active, so that the last |
| 1856 | * leaving stream may arm it. |
| 1857 | */ |
| 1858 | - if (!tick_isset(h2c->task->expire)) |
| 1859 | + if (h2c->task && !tick_isset(h2c->task->expire)) |
| 1860 | h2c_update_timeout(h2c); |
| 1861 | return; |
| 1862 | } |
| 1863 | @@ -3905,10 +3913,12 @@ next_frame: |
| 1864 | if (h2c->flags & H2_CF_IS_BACK) |
| 1865 | outlen = h2_make_htx_response(list, htx, &msgf, body_len); |
| 1866 | else |
| 1867 | - outlen = h2_make_htx_request(list, htx, &msgf, body_len); |
| 1868 | + outlen = h2_make_htx_request(list, htx, &msgf, body_len, |
| 1869 | + !!(((const struct session *)h2c->conn->owner)->fe->options2 & PR_O2_REQBUG_OK)); |
| 1870 | } else { |
| 1871 | /* HTTP/1 mode */ |
| 1872 | - outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len); |
| 1873 | + outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len, |
| 1874 | + !!(((const struct session *)h2c->conn->owner)->fe->options2 & PR_O2_REQBUG_OK)); |
| 1875 | if (outlen > 0) |
| 1876 | b_add(rxbuf, outlen); |
| 1877 | } |
| 1878 | diff --git a/src/mworker.c b/src/mworker.c |
| 1879 | index 487d071..8b646a7 100644 |
| 1880 | --- a/src/mworker.c |
| 1881 | +++ b/src/mworker.c |
| 1882 | @@ -173,6 +173,8 @@ int mworker_env_to_proc_list() |
| 1883 | |
| 1884 | } else if (strncmp(subtoken, "fd=", 3) == 0) { |
| 1885 | child->ipc_fd[0] = atoi(subtoken+3); |
| 1886 | + if (child->ipc_fd[0] > -1) |
| 1887 | + global.maxsock++; |
| 1888 | } else if (strncmp(subtoken, "pid=", 4) == 0) { |
| 1889 | child->pid = atoi(subtoken+4); |
| 1890 | } else if (strncmp(subtoken, "rpid=", 5) == 0) { |
| 1891 | @@ -388,6 +390,9 @@ static int mworker_pipe_register_per_thread() |
| 1892 | if (tid != 0) |
| 1893 | return 1; |
| 1894 | |
| 1895 | + if (proc_self->ipc_fd[1] < 0) /* proc_self was incomplete and we can't find the socketpair */ |
| 1896 | + return 1; |
| 1897 | + |
| 1898 | fcntl(proc_self->ipc_fd[1], F_SETFL, O_NONBLOCK); |
| 1899 | /* In multi-tread, we need only one thread to process |
| 1900 | * events on the pipe with master |
| 1901 | @@ -483,12 +488,15 @@ static int cli_io_handler_show_proc(struct appctx *appctx) |
| 1902 | struct stream_interface *si = appctx->owner; |
| 1903 | struct mworker_proc *child; |
| 1904 | int old = 0; |
| 1905 | - int up = now.tv_sec - proc_self->timestamp; |
| 1906 | + int up = date.tv_sec - proc_self->timestamp; |
| 1907 | char *uptime = NULL; |
| 1908 | |
| 1909 | if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) |
| 1910 | return 1; |
| 1911 | |
| 1912 | + if (up < 0) /* must never be negative because of clock drift */ |
| 1913 | + up = 0; |
| 1914 | + |
| 1915 | chunk_reset(&trash); |
| 1916 | |
| 1917 | chunk_printf(&trash, "#%-14s %-15s %-15s %-15s %-15s %-15s\n", "<PID>", "<type>", "<relative PID>", "<reloads>", "<uptime>", "<version>"); |
| 1918 | @@ -501,7 +509,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx) |
| 1919 | |
| 1920 | chunk_appendf(&trash, "# workers\n"); |
| 1921 | list_for_each_entry(child, &proc_list, list) { |
| 1922 | - up = now.tv_sec - child->timestamp; |
| 1923 | + up = date.tv_sec - child->timestamp; |
| 1924 | + if (up < 0) /* must never be negative because of clock drift */ |
| 1925 | + up = 0; |
| 1926 | |
| 1927 | if (!(child->options & PROC_O_TYPE_WORKER)) |
| 1928 | continue; |
| 1929 | @@ -523,7 +533,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx) |
| 1930 | |
| 1931 | chunk_appendf(&trash, "# old workers\n"); |
| 1932 | list_for_each_entry(child, &proc_list, list) { |
| 1933 | - up = now.tv_sec - child->timestamp; |
| 1934 | + up = date.tv_sec - child->timestamp; |
| 1935 | + if (up <= 0) /* must never be negative because of clock drift */ |
| 1936 | + up = 0; |
| 1937 | |
| 1938 | if (!(child->options & PROC_O_TYPE_WORKER)) |
| 1939 | continue; |
| 1940 | @@ -543,7 +555,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx) |
| 1941 | chunk_appendf(&trash, "# programs\n"); |
| 1942 | old = 0; |
| 1943 | list_for_each_entry(child, &proc_list, list) { |
| 1944 | - up = now.tv_sec - child->timestamp; |
| 1945 | + up = date.tv_sec - child->timestamp; |
| 1946 | + if (up < 0) /* must never be negative because of clock drift */ |
| 1947 | + up = 0; |
| 1948 | |
| 1949 | if (!(child->options & PROC_O_TYPE_PROG)) |
| 1950 | continue; |
| 1951 | @@ -561,7 +575,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx) |
| 1952 | if (old) { |
| 1953 | chunk_appendf(&trash, "# old programs\n"); |
| 1954 | list_for_each_entry(child, &proc_list, list) { |
| 1955 | - up = now.tv_sec - child->timestamp; |
| 1956 | + up = date.tv_sec - child->timestamp; |
| 1957 | + if (up < 0) /* must never be negative because of clock drift */ |
| 1958 | + up = 0; |
| 1959 | |
| 1960 | if (!(child->options & PROC_O_TYPE_PROG)) |
| 1961 | continue; |
| 1962 | diff --git a/src/namespace.c b/src/namespace.c |
| 1963 | index 89a968e..ad53d1e 100644 |
| 1964 | --- a/src/namespace.c |
| 1965 | +++ b/src/namespace.c |
| 1966 | @@ -54,6 +54,7 @@ static void netns_sig_stop(struct sig_handler *sh) |
| 1967 | entry = container_of(node, struct netns_entry, node); |
| 1968 | free(entry->node.key); |
| 1969 | close(entry->fd); |
| 1970 | + free(entry); |
| 1971 | node = next; |
| 1972 | } |
| 1973 | } |
| 1974 | diff --git a/src/proto_htx.c b/src/proto_htx.c |
| 1975 | index 51fdd47..91fdbfb 100644 |
| 1976 | --- a/src/proto_htx.c |
| 1977 | +++ b/src/proto_htx.c |
| 1978 | @@ -2642,11 +2642,12 @@ int htx_transform_header_str(struct stream* s, struct channel *chn, struct htx * |
| 1979 | struct ist name, const char *str, struct my_regex *re, int action) |
| 1980 | { |
| 1981 | struct http_hdr_ctx ctx; |
| 1982 | - struct buffer *output = get_trash_chunk(); |
| 1983 | |
| 1984 | /* find full header is action is ACT_HTTP_REPLACE_HDR */ |
| 1985 | ctx.blk = NULL; |
| 1986 | while (http_find_header(htx, name, &ctx, (action == ACT_HTTP_REPLACE_HDR))) { |
| 1987 | + struct buffer *output = get_trash_chunk(); |
| 1988 | + |
| 1989 | if (!regex_exec_match2(re, ctx.value.ptr, ctx.value.len, MAX_MATCH, pmatch, 0)) |
| 1990 | continue; |
| 1991 | |
| 1992 | diff --git a/src/proto_tcp.c b/src/proto_tcp.c |
| 1993 | index fc26a7c..5882e6b 100644 |
| 1994 | --- a/src/proto_tcp.c |
| 1995 | +++ b/src/proto_tcp.c |
| 1996 | @@ -1917,8 +1917,10 @@ static int bind_parse_interface(char **args, int cur_arg, struct proxy *px, stru |
| 1997 | } |
| 1998 | |
| 1999 | list_for_each_entry(l, &conf->listeners, by_bind) { |
| 2000 | - if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6) |
| 2001 | + if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6) { |
| 2002 | + free(l->interface); |
| 2003 | l->interface = strdup(args[cur_arg + 1]); |
| 2004 | + } |
| 2005 | } |
| 2006 | |
| 2007 | return 0; |
| 2008 | diff --git a/src/proxy.c b/src/proxy.c |
| 2009 | index cb5f8e1..d3d62ab 100644 |
| 2010 | --- a/src/proxy.c |
| 2011 | +++ b/src/proxy.c |
| 2012 | @@ -1022,8 +1022,8 @@ struct task *manage_proxy(struct task *t, void *context, unsigned short state) |
| 2013 | * to push to a new process and |
| 2014 | * we are free to flush the table. |
| 2015 | */ |
| 2016 | - stktable_trash_oldest(p->table, p->table->current); |
| 2017 | - pool_gc(NULL); |
| 2018 | + if (stktable_trash_oldest(p->table, p->table->current)) |
| 2019 | + pool_gc(NULL); |
| 2020 | } |
| 2021 | if (p->table->current) { |
| 2022 | /* some entries still remain, let's recheck in one second */ |
| 2023 | diff --git a/src/sample.c b/src/sample.c |
| 2024 | index e713f29..6153832 100644 |
| 2025 | --- a/src/sample.c |
| 2026 | +++ b/src/sample.c |
| 2027 | @@ -2451,12 +2451,12 @@ static inline long long int arith_add(long long int a, long long int b) |
| 2028 | * +------+----------+----------+ |
| 2029 | */ |
| 2030 | if ((a ^ b) >= 0) { |
| 2031 | - /* signs are differents. */ |
| 2032 | + /* signs are same. */ |
| 2033 | if (a < 0) { |
| 2034 | if (LLONG_MIN - a > b) |
| 2035 | return LLONG_MIN; |
| 2036 | } |
| 2037 | - if (LLONG_MAX - a < b) |
| 2038 | + else if (LLONG_MAX - a < b) |
| 2039 | return LLONG_MAX; |
| 2040 | } |
| 2041 | return a + b; |
| 2042 | diff --git a/src/server.c b/src/server.c |
| 2043 | index bef825d..5f1510f 100644 |
| 2044 | --- a/src/server.c |
| 2045 | +++ b/src/server.c |
| 2046 | @@ -1832,6 +1832,7 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl |
| 2047 | if (srv_tmpl) |
| 2048 | srv->srvrq = src->srvrq; |
| 2049 | |
| 2050 | + srv->netns = src->netns; |
| 2051 | srv->check.via_socks4 = src->check.via_socks4; |
| 2052 | srv->socks4_addr = src->socks4_addr; |
| 2053 | } |
| 2054 | @@ -3132,7 +3133,7 @@ static void srv_update_state(struct server *srv, int version, char **params) |
| 2055 | srv_adm_set_drain(srv); |
| 2056 | } |
| 2057 | |
| 2058 | - srv->last_change = date.tv_sec - srv_last_time_change; |
| 2059 | + srv->last_change = now.tv_sec - srv_last_time_change; |
| 2060 | srv->check.status = srv_check_status; |
| 2061 | srv->check.result = srv_check_result; |
| 2062 | |
| 2063 | @@ -4982,6 +4983,7 @@ static void srv_update_status(struct server *s) |
| 2064 | struct proxy *px = s->proxy; |
| 2065 | int prev_srv_count = s->proxy->srv_bck + s->proxy->srv_act; |
| 2066 | int srv_was_stopping = (s->cur_state == SRV_ST_STOPPING) || (s->cur_admin & SRV_ADMF_DRAIN); |
| 2067 | + enum srv_state srv_prev_state = s->cur_state; |
| 2068 | int log_level; |
| 2069 | struct buffer *tmptrash = NULL; |
| 2070 | |
| 2071 | @@ -4996,7 +4998,6 @@ static void srv_update_status(struct server *s) |
| 2072 | s->next_admin = s->cur_admin; |
| 2073 | |
| 2074 | if ((s->cur_state != SRV_ST_STOPPED) && (s->next_state == SRV_ST_STOPPED)) { |
| 2075 | - s->last_change = now.tv_sec; |
| 2076 | if (s->proxy->lbprm.set_server_status_down) |
| 2077 | s->proxy->lbprm.set_server_status_down(s); |
| 2078 | |
| 2079 | @@ -5027,13 +5028,9 @@ static void srv_update_status(struct server *s) |
| 2080 | free_trash_chunk(tmptrash); |
| 2081 | tmptrash = NULL; |
| 2082 | } |
| 2083 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2084 | - set_backend_down(s->proxy); |
| 2085 | - |
| 2086 | s->counters.down_trans++; |
| 2087 | } |
| 2088 | else if ((s->cur_state != SRV_ST_STOPPING) && (s->next_state == SRV_ST_STOPPING)) { |
| 2089 | - s->last_change = now.tv_sec; |
| 2090 | if (s->proxy->lbprm.set_server_status_down) |
| 2091 | s->proxy->lbprm.set_server_status_down(s); |
| 2092 | |
| 2093 | @@ -5057,22 +5054,10 @@ static void srv_update_status(struct server *s) |
| 2094 | free_trash_chunk(tmptrash); |
| 2095 | tmptrash = NULL; |
| 2096 | } |
| 2097 | - |
| 2098 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2099 | - set_backend_down(s->proxy); |
| 2100 | } |
| 2101 | else if (((s->cur_state != SRV_ST_RUNNING) && (s->next_state == SRV_ST_RUNNING)) |
| 2102 | || ((s->cur_state != SRV_ST_STARTING) && (s->next_state == SRV_ST_STARTING))) { |
| 2103 | - if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) { |
| 2104 | - if (s->proxy->last_change < now.tv_sec) // ignore negative times |
| 2105 | - s->proxy->down_time += now.tv_sec - s->proxy->last_change; |
| 2106 | - s->proxy->last_change = now.tv_sec; |
| 2107 | - } |
| 2108 | - |
| 2109 | - if (s->cur_state == SRV_ST_STOPPED && s->last_change < now.tv_sec) // ignore negative times |
| 2110 | - s->down_time += now.tv_sec - s->last_change; |
| 2111 | |
| 2112 | - s->last_change = now.tv_sec; |
| 2113 | if (s->next_state == SRV_ST_STARTING && s->warmup) |
| 2114 | task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)))); |
| 2115 | |
| 2116 | @@ -5118,9 +5103,6 @@ static void srv_update_status(struct server *s) |
| 2117 | free_trash_chunk(tmptrash); |
| 2118 | tmptrash = NULL; |
| 2119 | } |
| 2120 | - |
| 2121 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2122 | - set_backend_down(s->proxy); |
| 2123 | } |
| 2124 | else if (s->cur_eweight != s->next_eweight) { |
| 2125 | /* now propagate the status change to any LB algorithms */ |
| 2126 | @@ -5134,9 +5116,6 @@ static void srv_update_status(struct server *s) |
| 2127 | if (px->lbprm.set_server_status_down) |
| 2128 | px->lbprm.set_server_status_down(s); |
| 2129 | } |
| 2130 | - |
| 2131 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2132 | - set_backend_down(s->proxy); |
| 2133 | } |
| 2134 | |
| 2135 | s->next_admin = next_admin; |
| 2136 | @@ -5174,13 +5153,9 @@ static void srv_update_status(struct server *s) |
| 2137 | free_trash_chunk(tmptrash); |
| 2138 | tmptrash = NULL; |
| 2139 | } |
| 2140 | - /* commit new admin status */ |
| 2141 | - |
| 2142 | - s->cur_admin = s->next_admin; |
| 2143 | } |
| 2144 | else { /* server was still running */ |
| 2145 | check->health = 0; /* failure */ |
| 2146 | - s->last_change = now.tv_sec; |
| 2147 | |
| 2148 | s->next_state = SRV_ST_STOPPED; |
| 2149 | if (s->proxy->lbprm.set_server_status_down) |
| 2150 | @@ -5213,9 +5188,6 @@ static void srv_update_status(struct server *s) |
| 2151 | free_trash_chunk(tmptrash); |
| 2152 | tmptrash = NULL; |
| 2153 | } |
| 2154 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2155 | - set_backend_down(s->proxy); |
| 2156 | - |
| 2157 | s->counters.down_trans++; |
| 2158 | } |
| 2159 | } |
| 2160 | @@ -5235,7 +5207,6 @@ static void srv_update_status(struct server *s) |
| 2161 | * that the server might still be in drain mode, which is naturally dealt |
| 2162 | * with by the lower level functions. |
| 2163 | */ |
| 2164 | - |
| 2165 | if (s->check.state & CHK_ST_ENABLED) { |
| 2166 | s->check.state &= ~CHK_ST_PAUSED; |
| 2167 | check->health = check->rise; /* start OK but check immediately */ |
| 2168 | @@ -5248,7 +5219,6 @@ static void srv_update_status(struct server *s) |
| 2169 | s->next_state = SRV_ST_STOPPING; |
| 2170 | } |
| 2171 | else { |
| 2172 | - s->last_change = now.tv_sec; |
| 2173 | s->next_state = SRV_ST_STARTING; |
| 2174 | if (s->slowstart > 0) { |
| 2175 | if (s->warmup) |
| 2176 | @@ -5306,11 +5276,6 @@ static void srv_update_status(struct server *s) |
| 2177 | px->lbprm.set_server_status_down(s); |
| 2178 | } |
| 2179 | |
| 2180 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2181 | - set_backend_down(s->proxy); |
| 2182 | - else if (!prev_srv_count && (s->proxy->srv_bck || s->proxy->srv_act)) |
| 2183 | - s->proxy->last_change = now.tv_sec; |
| 2184 | - |
| 2185 | /* If the server is set with "on-marked-up shutdown-backup-sessions", |
| 2186 | * and it's not a backup server and its effective weight is > 0, |
| 2187 | * then it can accept new connections, so we shut down all streams |
| 2188 | @@ -5380,15 +5345,12 @@ static void srv_update_status(struct server *s) |
| 2189 | } |
| 2190 | } |
| 2191 | /* don't report anything when leaving drain mode and remaining in maintenance */ |
| 2192 | - |
| 2193 | - s->cur_admin = s->next_admin; |
| 2194 | } |
| 2195 | |
| 2196 | if (!(s->next_admin & SRV_ADMF_MAINT)) { |
| 2197 | if (!(s->cur_admin & SRV_ADMF_DRAIN) && (s->next_admin & SRV_ADMF_DRAIN)) { |
| 2198 | /* drain state is applied only if not yet in maint */ |
| 2199 | |
| 2200 | - s->last_change = now.tv_sec; |
| 2201 | if (px->lbprm.set_server_status_down) |
| 2202 | px->lbprm.set_server_status_down(s); |
| 2203 | |
| 2204 | @@ -5416,26 +5378,14 @@ static void srv_update_status(struct server *s) |
| 2205 | free_trash_chunk(tmptrash); |
| 2206 | tmptrash = NULL; |
| 2207 | } |
| 2208 | - |
| 2209 | - if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2210 | - set_backend_down(s->proxy); |
| 2211 | } |
| 2212 | else if ((s->cur_admin & SRV_ADMF_DRAIN) && !(s->next_admin & SRV_ADMF_DRAIN)) { |
| 2213 | /* OK completely leaving drain mode */ |
| 2214 | - if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) { |
| 2215 | - if (s->proxy->last_change < now.tv_sec) // ignore negative times |
| 2216 | - s->proxy->down_time += now.tv_sec - s->proxy->last_change; |
| 2217 | - s->proxy->last_change = now.tv_sec; |
| 2218 | - } |
| 2219 | - |
| 2220 | - if (s->last_change < now.tv_sec) // ignore negative times |
| 2221 | - s->down_time += now.tv_sec - s->last_change; |
| 2222 | - s->last_change = now.tv_sec; |
| 2223 | server_recalc_eweight(s, 0); |
| 2224 | |
| 2225 | tmptrash = alloc_trash_chunk(); |
| 2226 | if (tmptrash) { |
| 2227 | - if (!(s->next_admin & SRV_ADMF_FDRAIN)) { |
| 2228 | + if (s->cur_admin & SRV_ADMF_FDRAIN) { |
| 2229 | chunk_printf(tmptrash, |
| 2230 | "%sServer %s/%s is %s (leaving forced drain)", |
| 2231 | s->flags & SRV_F_BACKUP ? "Backup " : "", |
| 2232 | @@ -5499,15 +5449,38 @@ static void srv_update_status(struct server *s) |
| 2233 | free_trash_chunk(tmptrash); |
| 2234 | tmptrash = NULL; |
| 2235 | } |
| 2236 | - |
| 2237 | - /* commit new admin status */ |
| 2238 | - |
| 2239 | - s->cur_admin = s->next_admin; |
| 2240 | } |
| 2241 | } |
| 2242 | |
| 2243 | /* Re-set log strings to empty */ |
| 2244 | *s->adm_st_chg_cause = 0; |
| 2245 | + |
| 2246 | + /* explicitly commit state changes (even if it was already applied implicitly |
| 2247 | + * by some lb state change function), so we don't miss anything |
| 2248 | + */ |
| 2249 | + srv_lb_commit_status(s); |
| 2250 | + |
| 2251 | + /* check if server stats must be updated due the the server state change */ |
| 2252 | + if (srv_prev_state != s->cur_state) { |
| 2253 | + if (srv_prev_state == SRV_ST_STOPPED) { |
| 2254 | + /* server was down and no longer is */ |
| 2255 | + if (s->last_change < now.tv_sec) // ignore negative times |
| 2256 | + s->down_time += now.tv_sec - s->last_change; |
| 2257 | + } |
| 2258 | + s->last_change = now.tv_sec; |
| 2259 | + } |
| 2260 | + |
| 2261 | + /* check if backend stats must be updated due to the server state change */ |
| 2262 | + if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) |
| 2263 | + set_backend_down(s->proxy); /* backend going down */ |
| 2264 | + else if (!prev_srv_count && (s->proxy->srv_bck || s->proxy->srv_act)) { |
| 2265 | + /* backend was down and is back up again: |
| 2266 | + * no helper function, updating last_change and backend downtime stats |
| 2267 | + */ |
| 2268 | + if (s->proxy->last_change < now.tv_sec) // ignore negative times |
| 2269 | + s->proxy->down_time += now.tv_sec - s->proxy->last_change; |
| 2270 | + s->proxy->last_change = now.tv_sec; |
| 2271 | + } |
| 2272 | } |
| 2273 | |
| 2274 | struct task *srv_cleanup_toremove_connections(struct task *task, void *context, unsigned short state) |
| 2275 | diff --git a/src/ssl_sock.c b/src/ssl_sock.c |
| 2276 | index b33580f..ac1109f 100644 |
| 2277 | --- a/src/ssl_sock.c |
| 2278 | +++ b/src/ssl_sock.c |
| 2279 | @@ -1162,7 +1162,7 @@ int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg) |
| 2280 | if (!ocsp || |
| 2281 | !ocsp->response.area || |
| 2282 | !ocsp->response.data || |
| 2283 | - (ocsp->expire < now.tv_sec)) |
| 2284 | + (ocsp->expire < date.tv_sec)) |
| 2285 | return SSL_TLSEXT_ERR_NOACK; |
| 2286 | |
| 2287 | ssl_buf = OPENSSL_malloc(ocsp->response.data); |
| 2288 | diff --git a/src/stream_interface.c b/src/stream_interface.c |
| 2289 | index 1adecf3..09e62d9 100644 |
| 2290 | --- a/src/stream_interface.c |
| 2291 | +++ b/src/stream_interface.c |
| 2292 | @@ -955,6 +955,22 @@ void si_sync_send(struct stream_interface *si) |
| 2293 | return; |
| 2294 | |
| 2295 | si_cs_send(cs); |
| 2296 | + |
| 2297 | + if (likely(oc->flags & CF_WRITE_ACTIVITY)) { |
| 2298 | + struct channel *ic = si_ic(si); |
| 2299 | + |
| 2300 | + if (tick_isset(ic->rex) && !(si->flags & SI_FL_INDEP_STR)) { |
| 2301 | + /* Note: to prevent the client from expiring read timeouts |
| 2302 | + * during writes, we refresh it. We only do this if the |
| 2303 | + * interface is not configured for "independent streams", |
| 2304 | + * because for some applications it's better not to do this, |
| 2305 | + * for instance when continuously exchanging small amounts |
| 2306 | + * of data which can full the socket buffers long before a |
| 2307 | + * write timeout is detected. |
| 2308 | + */ |
| 2309 | + ic->rex = tick_add_ifset(now_ms, ic->rto); |
| 2310 | + } |
| 2311 | + } |
| 2312 | } |
| 2313 | |
| 2314 | /* Updates at once the channel flags, and timers of both stream interfaces of a |
| 2315 | diff --git a/src/tcp_rules.c b/src/tcp_rules.c |
| 2316 | index 229c102..2ac0234 100644 |
| 2317 | --- a/src/tcp_rules.c |
| 2318 | +++ b/src/tcp_rules.c |
| 2319 | @@ -123,7 +123,7 @@ int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit) |
| 2320 | * - if one rule returns KO, then return KO |
| 2321 | */ |
| 2322 | |
| 2323 | - if ((req->flags & (CF_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(req, global.tune.maxrewrite) || |
| 2324 | + if ((req->flags & (CF_SHUTR|CF_READ_ERROR)) || channel_full(req, global.tune.maxrewrite) || |
| 2325 | si_rx_blocked_room(chn_prod(req)) || |
| 2326 | !s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms)) |
| 2327 | partial = SMP_OPT_FINAL; |
| 2328 | @@ -301,7 +301,7 @@ int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit) |
| 2329 | * - if one rule returns OK, then return OK |
| 2330 | * - if one rule returns KO, then return KO |
| 2331 | */ |
| 2332 | - if ((rep->flags & (CF_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(rep, global.tune.maxrewrite) || |
| 2333 | + if ((rep->flags & (CF_SHUTR|CF_READ_ERROR)) || channel_full(rep, global.tune.maxrewrite) || |
| 2334 | si_rx_blocked_room(chn_prod(rep)) || |
| 2335 | !s->be->tcp_rep.inspect_delay || tick_is_expired(rep->analyse_exp, now_ms)) |
| 2336 | partial = SMP_OPT_FINAL; |
| 2337 | diff --git a/src/time.c b/src/time.c |
| 2338 | index c2bdbdc..7d89008 100644 |
| 2339 | --- a/src/time.c |
| 2340 | +++ b/src/time.c |
| 2341 | @@ -26,7 +26,8 @@ THREAD_LOCAL unsigned int samp_time; /* total elapsed time over current |
| 2342 | THREAD_LOCAL unsigned int idle_time; /* total idle time over current sample */ |
| 2343 | THREAD_LOCAL struct timeval now; /* internal date is a monotonic function of real clock */ |
| 2344 | THREAD_LOCAL struct timeval date; /* the real current date */ |
| 2345 | -struct timeval start_date; /* the process's start date */ |
| 2346 | +struct timeval start_date; /* the process's start date */ |
| 2347 | +struct timeval ready_date; /* date when the process was considered ready */ |
| 2348 | THREAD_LOCAL struct timeval before_poll; /* system date before calling poll() */ |
| 2349 | THREAD_LOCAL struct timeval after_poll; /* system date after leaving poll() */ |
| 2350 |

Thank you for the MP, Athos.
Also, thank you very much for the extremely well written MRE bug! It has made reviewing the changes much easier :-).
I agree with your analysis of the situation there. One of the major fixes being introduced here and in the Jammy MRE ("http: reject any empty content-length header value") is actually already part of the haproxy package we ship our users, because it's been fixed by the security team. Therefore, we're only left with one new major fix being backported.
Anyway, the changes LGTM. dep8 tests are passing (although they're somewhat superficial, as explained in the MRE document). I'm relying on the Microsoft Github's actions to have a better picture of the testing being done here. As I mentioned to you in the channel, I believe it may be worth patching the repository to make the "Spec Compliance" test run. It seems to be an important test and I'd like to see its results.
Otherwise, there's not much to say here. I also agree with leaving Lunar out of this MRE round.
I'm reviewing this MP as Needs Information just because of the "Spec Compliance" test. Let me know what you think.
Thanks.