├── Changelog.txt ├── LICENSE ├── README.md ├── basic.t ├── config ├── docs ├── sticky.pdf └── sticky.vsd ├── ngx_http_sticky_misc.c ├── ngx_http_sticky_misc.h ├── ngx_http_sticky_module.c └── patches ├── cookies.patch └── upstream_check.patch /Changelog.txt: -------------------------------------------------------------------------------- 1 | 2 | 1.2.5 - 2014-07-07 3 | - setting path-defaults to / 4 | fixing issue 5 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/7/leaving-cookie-path-empty-in-module 6 | 7 | 1.2.4 - 2014-04-18 8 | - fixed an compiling-issue on some systems (SLES, CentOS) 9 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/5/nginx-compile-warning 10 | 11 | 1.2.3 - 2014-03-10 12 | - sticky-module might work now with upstream-check-module 13 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/3/patch-to-allow 14 | 15 | 1.2.2 - 2014-03-04 16 | 17 | - use "Expires" instead of "Max-Age" directive to set cookie expiration 18 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/2/nginx-sticky-module-uses-max-age-directive 19 | - fix a bug due to a change in nginx-api (>= 1.5.8) 20 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/1/nginx-158-api-change-for-ngx_sock_ntop 21 | 22 | 23 | 1.2.1 - 2014-02-20 24 | 25 | - cloned from: https://code.google.com/p/nginx-sticky-module/ 26 | - route-cookie might be configured to be httponly/secure 27 | patch included: https://code.google.com/p/nginx-sticky-module/issues/detail?id=24 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net) 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nginx Sticky Module 2 | 3 | 4 | modified and extended version; see Changelog.txt 5 | 6 | # Description 7 | 8 | A nginx module to add a sticky cookie to be always forwarded to the same 9 | upstream server. 10 | 11 | When dealing with several backend servers, it's sometimes useful that one 12 | client (browser) is always served by the same backend server 13 | (for session persistance for example). 14 | 15 | Using a persistance by IP (with the ip_hash upstream module) is maybe not 16 | a good idea because there could be situations where a lot of different 17 | browsers are coming with the same IP address (behind proxies)and the load 18 | balancing system won't be fair. 19 | 20 | Using a cookie to track the upstream server makes each browser unique. 21 | 22 | When the sticky module can't apply, it switchs back to the classic Round Robin 23 | Upstream or returns a "Bad Gateway" (depending on the no_fallback flag). 24 | 25 | Sticky module can't apply when cookies are not supported by the browser 26 | 27 | > Sticky module is based on a "best effort" algorithm. Its aim is not to handle 28 | > security somehow. It's been made to ensure that normal users are always 29 | > redirected to the same backend server: that's all! 30 | 31 | # Installation 32 | 33 | You'll need to re-compile Nginx from source to include this module. 34 | Modify your compile of Nginx by adding the following directive 35 | (modified to suit your path of course): 36 | 37 | ./configure ... --add-module=/absolute/path/to/nginx-sticky-module-ng 38 | make 39 | make install 40 | 41 | # Usage 42 | 43 | upstream { 44 | sticky; 45 | server 127.0.0.1:9000; 46 | server 127.0.0.1:9001; 47 | server 127.0.0.1:9002; 48 | } 49 | 50 | sticky [hash=index|md5|sha1] [no_fallback] [transfer] [delimiter=.] 51 | [name=route] [domain=.foo.bar] [path=/] [expires=1h] [secure] [httponly]; 52 | or 53 | sticky [hmac=md5|sha1 hmac_key=] [no_fallback] [transfer] [delimiter=.] 54 | [name=route] [domain=.foo.bar] [path=/] [expires=1h] [secure] [httponly]; 55 | or 56 | sticky [text=raw] [no_fallback] [transfer] [delimiter=.] 57 | [name=route] [domain=.foo.bar] [path=/] [expires=1h] [secure] [httponly]; 58 | 59 | Server selection algorithm: 60 | - `hash`: the hash mechanism to encode upstream server. It can't be used with hmac or text. 61 | default: `md5` 62 | 63 | - `md5`|`sha1`: well known hash 64 | - `index`: it's not hashed, an in-memory index is used instead, it's quicker and the overhead is shorter 65 | Warning: the matching against upstream servers list 66 | is inconsistent. So, at reload, if upstreams servers 67 | has changed, index values are not guaranted to 68 | correspond to the same server as before! 69 | USE IT WITH CAUTION and only if you need to! 70 | 71 | - `hmac`: the HMAC hash mechanism to encode upstream server 72 | It's like the hash mechanism but it uses hmac_key 73 | to secure the hashing. It can't be used with hash or text. 74 | `md5`|`sha1`: well known hash 75 | 76 | - `hmac_key`: the key to use with hmac. It's mandatory when hmac is set 77 | 78 | - `no_fallback`: when this flag is set, nginx will return a 502 (Bad Gateway or 79 | Proxy Error) if a request comes with a cookie and the 80 | corresponding backend is unavailable. You can set it to the 81 | upstream block, or set `sticky_no_fallback` in a server or 82 | location block. 83 | 84 | - `transfer`: when this flag is set, nginx adds a cookie from the backend to the sticky cookie 85 | default: space 86 | 87 | Example for remove peer part from cookie before sand it to backend: 88 | ``` 89 | server { 90 | # ... 91 | set $jsessionid $cookie_JSESSIONID; 92 | if ($cookie_JSESSIONID ~ "^[^\s]+\s(.*)$") { 93 | set $jsessionid $1; 94 | } 95 | location / { 96 | proxy_set_header Cookie "JSESSIONID=$jsessionid"; 97 | proxy_pass http://backend; 98 | } 99 | } 100 | ``` 101 | 102 | - delimiter: delimiter to add a cookie from the backend 103 | 104 | Cookie settings: 105 | - `name`: the name of the cookie used to track the persistant upstream srv; 106 | default: `route` 107 | 108 | - `domain`: the domain in which the cookie will be valid 109 | default: none. Let the browser handle this. 110 | 111 | - `path`: the path in which the cookie will be valid 112 | default: `/` 113 | 114 | - `expires`: the validity duration of the cookie 115 | default: nothing. It's a session cookie. 116 | restriction: must be a duration greater than one second 117 | 118 | - `secure`: enable secure cookies; transferred only via https 119 | - `httponly`: enable cookies not to be leaked via js 120 | - `hide_cookie`: does not send cookies to the client side 121 | This is to use cookies exclusively for routing only. 122 | You can set it to the upstream block, or set `sticky_hide_cookie` in a server or 123 | location block. 124 | 125 | # Detail Mechanism 126 | 127 | - see docs/sticky.{vsd,pdf} 128 | 129 | # Issues and Warnings: 130 | 131 | - when using different upstream-configs with stickyness that use the same domain but 132 | refer to different location - configs it might be wise to set a different path / route - 133 | option on each of this upstream-configs like described here: 134 | https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/7/leaving-cookie-path-empty-in-module 135 | 136 | - sticky module does not work with the "backup" option of the "server" configuration item. 137 | - sticky module might work with the nginx_http_upstream_check_module (up from version 1.2.3) 138 | - sticky module does not modify cookie from a client to the backend if "transfer" flag is set. 139 | 140 | 141 | # Contributing 142 | 143 | - please send/suggest patches as diffs 144 | - tickets and issues here: https://bitbucket.org/nginx-goodies/nginx-sticky-session-ng 145 | 146 | 147 | # Downloads 148 | 149 | - tarballs are available via tags from the repo: https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/downloads 150 | 151 | 152 | # TODO 153 | 154 | see Todo.md 155 | 156 | # Authors & Credits 157 | 158 | - Jerome Loyet, initial module 159 | - Markus Linnala, httponly/secure-cookies-patch 160 | - Peter Bowey, Nginx 1.5.8 API-Change 161 | - Michael Chernyak for Max-Age-Patch 162 | - anybody who suggested a patch, created an issue on bitbucket or helped improving this module 163 | 164 | 165 | 166 | # Copyright & License 167 | 168 | This module is licenced under the BSD license. 169 | 170 | Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net) 171 | Copyright (C) 2014 Markus Manzke (goodman at nginx-goodies dot com) 172 | 173 | 174 | Redistribution and use in source and binary forms, with or without 175 | modification, are permitted provided that the following conditions 176 | are met: 177 | 178 | 1. Redistributions of source code must retain the above copyright 179 | notice, this list of conditions and the following disclaimer. 180 | 181 | 2. Redistributions in binary form must reproduce the above copyright 182 | notice, this list of conditions and the following disclaimer in the 183 | documentation and/or other materials provided with the distribution. 184 | 185 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 186 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 187 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 188 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 189 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 190 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 191 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 192 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 193 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 194 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 195 | SUCH DAMAGE. 196 | 197 | -------------------------------------------------------------------------------- /basic.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; # 'no_plan'; 5 | use URI::Escape; 6 | 7 | repeat_each(1); 8 | 9 | plan tests => repeat_each() * 2 * blocks(); 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: hash=md5 16 | --- http_config 17 | upstream backend { 18 | server localhost:$TEST_NGINX_SERVER_PORT; 19 | server 127.0.0.2:80; 20 | server 127.0.0.3:80; 21 | server 127.0.0.4:80; 22 | server 127.0.0.5:80; 23 | sticky name=route hash=md5; 24 | } 25 | --- config 26 | location /backend { 27 | rewrite /backend /frontend break; 28 | proxy_pass http://backend; 29 | proxy_set_header Host $host; 30 | } 31 | location /frontend { 32 | echo -n $echo_client_request_headers; 33 | } 34 | --- request 35 | GET /backend 36 | --- response_headers 37 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92 38 | 39 | === TEST 2: hash=sha1 40 | --- http_config 41 | upstream backend { 42 | server localhost:$TEST_NGINX_SERVER_PORT; 43 | server 127.0.0.2:80; 44 | server 127.0.0.3:80; 45 | server 127.0.0.4:80; 46 | server 127.0.0.5:80; 47 | sticky name=route hash=sha1; 48 | } 49 | --- config 50 | location /backend { 51 | rewrite /backend /frontend break; 52 | proxy_pass http://backend; 53 | proxy_set_header Host $host; 54 | } 55 | location /frontend { 56 | echo -n $echo_client_request_headers; 57 | } 58 | --- request 59 | GET /backend 60 | --- response_headers 61 | Set-Cookie: route=4262de333a18749d31416a617184734678797276 62 | 63 | === TEST 3: hmac=md5 64 | --- http_config 65 | upstream backend { 66 | server localhost:$TEST_NGINX_SERVER_PORT; 67 | server 127.0.0.2:80; 68 | server 127.0.0.3:80; 69 | server 127.0.0.4:80; 70 | server 127.0.0.5:80; 71 | sticky name=route hmac=md5 hmac_key=secret; 72 | } 73 | --- config 74 | location /backend { 75 | rewrite /backend /frontend break; 76 | proxy_pass http://backend; 77 | proxy_set_header Host $host; 78 | } 79 | location /frontend { 80 | echo -n $echo_client_request_headers; 81 | } 82 | --- request 83 | GET /backend 84 | --- response_headers 85 | Set-Cookie: route=d20fd0a9eb6864058781ed6104e4c9fd 86 | 87 | === TEST 4: hmac=sha1 88 | --- http_config 89 | upstream backend { 90 | server localhost:$TEST_NGINX_SERVER_PORT; 91 | server 127.0.0.2:80; 92 | server 127.0.0.3:80; 93 | server 127.0.0.4:80; 94 | server 127.0.0.5:80; 95 | sticky name=route hmac=sha1 hmac_key=secret; 96 | } 97 | --- config 98 | location /backend { 99 | rewrite /backend /frontend break; 100 | proxy_pass http://backend; 101 | proxy_set_header Host $host; 102 | } 103 | location /frontend { 104 | echo -n $echo_client_request_headers; 105 | } 106 | --- request 107 | GET /backend 108 | --- response_headers 109 | Set-Cookie: route=34734c8d4b451151897b62db281c0b055e035adc 110 | 111 | === TEST 5: hmac=sha1 hmac_key=secret2 112 | --- http_config 113 | upstream backend { 114 | server localhost:$TEST_NGINX_SERVER_PORT; 115 | server 127.0.0.2:80; 116 | server 127.0.0.3:80; 117 | server 127.0.0.4:80; 118 | server 127.0.0.5:80; 119 | sticky name=route hmac=sha1 hmac_key=secret2; 120 | } 121 | --- config 122 | location /backend { 123 | rewrite /backend /frontend break; 124 | proxy_pass http://backend; 125 | proxy_set_header Host $host; 126 | } 127 | location /frontend { 128 | echo -n $echo_client_request_headers; 129 | } 130 | --- request 131 | GET /backend 132 | --- response_headers 133 | Set-Cookie: route=959bfc3973750a198925a3aedf9570f5d9b7e6f8 134 | 135 | === TEST 6: domain=.example.com 136 | --- http_config 137 | upstream backend { 138 | server localhost:$TEST_NGINX_SERVER_PORT; 139 | server 127.0.0.2:80; 140 | server 127.0.0.3:80; 141 | server 127.0.0.4:80; 142 | server 127.0.0.5:80; 143 | sticky domain=.example.com; 144 | } 145 | --- config 146 | location /backend { 147 | rewrite /backend /frontend break; 148 | proxy_pass http://backend; 149 | proxy_set_header Host $host; 150 | } 151 | location /frontend { 152 | echo -n $echo_client_request_headers; 153 | } 154 | --- request 155 | GET /backend 156 | --- response_headers 157 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Domain=.example.com 158 | 159 | === TEST 7: path=/example 160 | --- http_config 161 | upstream backend { 162 | server localhost:$TEST_NGINX_SERVER_PORT; 163 | server 127.0.0.2:80; 164 | server 127.0.0.3:80; 165 | server 127.0.0.4:80; 166 | server 127.0.0.5:80; 167 | sticky path=/example; 168 | } 169 | --- config 170 | location /backend { 171 | rewrite /backend /frontend break; 172 | proxy_pass http://backend; 173 | proxy_set_header Host $host; 174 | } 175 | location /frontend { 176 | echo -n $echo_client_request_headers; 177 | } 178 | --- request 179 | GET /backend 180 | --- response_headers 181 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Path=/example 182 | 183 | === TEST 8: expires=1h 184 | --- http_config 185 | upstream backend { 186 | server localhost:$TEST_NGINX_SERVER_PORT; 187 | server 127.0.0.2:80; 188 | server 127.0.0.3:80; 189 | server 127.0.0.4:80; 190 | server 127.0.0.5:80; 191 | sticky expires=1h; 192 | } 193 | --- config 194 | location /backend { 195 | rewrite /backend /frontend break; 196 | proxy_pass http://backend; 197 | proxy_set_header Host $host; 198 | } 199 | location /frontend { 200 | echo -n $echo_client_request_headers; 201 | } 202 | --- request 203 | GET /backend 204 | --- response_headers 205 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Max-Age=3600 206 | 207 | === TEST 9: text=md5 208 | --- http_config 209 | upstream backend { 210 | server localhost:$TEST_NGINX_SERVER_PORT; 211 | server 127.0.0.2:80; 212 | server 127.0.0.3:80; 213 | server 127.0.0.4:80; 214 | server 127.0.0.5:80; 215 | sticky name=route text=md5; 216 | } 217 | --- config 218 | location /backend { 219 | rewrite /backend /frontend break; 220 | proxy_pass http://backend; 221 | proxy_set_header Host $host; 222 | } 223 | location /frontend { 224 | echo -n $echo_client_request_headers; 225 | } 226 | --- request 227 | GET /backend 228 | --- response_headers 229 | Set-Cookie: route=f6082e846954099610d58161bf189f37 230 | 231 | === TEST 10: text=sha1 232 | --- http_config 233 | upstream backend { 234 | server localhost:$TEST_NGINX_SERVER_PORT; 235 | server 127.0.0.2:80; 236 | server 127.0.0.3:80; 237 | server 127.0.0.4:80; 238 | server 127.0.0.5:80; 239 | sticky name=route text=sha1; 240 | } 241 | --- config 242 | location /backend { 243 | rewrite /backend /frontend break; 244 | proxy_pass http://backend; 245 | proxy_set_header Host $host; 246 | } 247 | location /frontend { 248 | echo -n $echo_client_request_headers; 249 | } 250 | --- request 251 | GET /backend 252 | --- response_headers 253 | Set-Cookie: route=17305d40bf37f65329da1850efddd32840891e32 254 | 255 | === TEST 11: text=raw 256 | --- http_config 257 | upstream backend { 258 | server localhost:$TEST_NGINX_SERVER_PORT; 259 | server 127.0.0.2:80; 260 | server 127.0.0.3:80; 261 | server 127.0.0.4:80; 262 | server 127.0.0.5:80; 263 | sticky name=route text=raw; 264 | } 265 | --- config 266 | location /backend { 267 | rewrite /backend /frontend break; 268 | proxy_pass http://backend; 269 | proxy_set_header Host $host; 270 | } 271 | location /frontend { 272 | echo -n $echo_client_request_headers; 273 | } 274 | --- request 275 | GET /backend 276 | --- response_headers 277 | Set-Cookie: route=127.0.0.1:1984 278 | 279 | === TEST 12: no_fallback 280 | --- http_config 281 | upstream backend { 282 | server localhost:$TEST_NGINX_SERVER_PORT; 283 | server 127.0.0.2:80; 284 | server 127.0.0.3:80; 285 | server 127.0.0.4:80; 286 | server 127.0.0.5:80; 287 | sticky no_fallback; 288 | } 289 | --- config 290 | location /backend { 291 | rewrite /backend /frontend break; 292 | proxy_pass http://backend; 293 | proxy_set_header Host $host; 294 | } 295 | location /frontend { 296 | echo -n $echo_client_request_headers; 297 | } 298 | --- request 299 | GET /backend 300 | --- response_headers 301 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92 302 | 303 | === TEST 13: secure 304 | --- http_config 305 | upstream backend { 306 | server localhost:$TEST_NGINX_SERVER_PORT; 307 | server 127.0.0.2:80; 308 | server 127.0.0.3:80; 309 | server 127.0.0.4:80; 310 | server 127.0.0.5:80; 311 | sticky secure; 312 | } 313 | --- config 314 | location /backend { 315 | rewrite /backend /frontend break; 316 | proxy_pass http://backend; 317 | proxy_set_header Host $host; 318 | } 319 | location /frontend { 320 | echo -n $echo_client_request_headers; 321 | } 322 | --- request 323 | GET /backend 324 | --- response_headers 325 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Secure 326 | 327 | === TEST 14: httponly 328 | --- http_config 329 | upstream backend { 330 | server localhost:$TEST_NGINX_SERVER_PORT; 331 | server 127.0.0.2:80; 332 | server 127.0.0.3:80; 333 | server 127.0.0.4:80; 334 | server 127.0.0.5:80; 335 | sticky httponly; 336 | } 337 | --- config 338 | location /backend { 339 | rewrite /backend /frontend break; 340 | proxy_pass http://backend; 341 | proxy_set_header Host $host; 342 | } 343 | location /frontend { 344 | echo -n $echo_client_request_headers; 345 | } 346 | --- request 347 | GET /backend 348 | --- response_headers 349 | Set-Cookie: route=908c1a9fb15095f454c085282da20d92; HttpOnly 350 | 351 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_sticky_module 2 | HTTP_STICKY_DEPS="$ngx_addon_dir/ngx_http_sticky_misc.h" 3 | HTTP_STICKY_SRCS="$ngx_addon_dir/ngx_http_sticky_module.c $ngx_addon_dir/ngx_http_sticky_misc.c" 4 | USE_MD5=YES 5 | USE_SHA1=YES 6 | 7 | if test -n "$ngx_module_link"; then 8 | ngx_module_type=HTTP 9 | ngx_module_name=$ngx_addon_name 10 | ngx_module_incs= 11 | ngx_module_deps="$HTTP_STICKY_DEPS" 12 | ngx_module_srcs="$HTTP_STICKY_SRCS" 13 | ngx_module_libs= 14 | 15 | . auto/module 16 | else 17 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 18 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $HTTP_STICKY_DEPS" 19 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $HTTP_STICKY_SRCS" 20 | fi 21 | -------------------------------------------------------------------------------- /docs/sticky.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Refinitiv/nginx-sticky-module-ng/6fe17af5a07aa6782d0080d4255b0f4a9de18125/docs/sticky.pdf -------------------------------------------------------------------------------- /docs/sticky.vsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Refinitiv/nginx-sticky-module-ng/6fe17af5a07aa6782d0080d4255b0f4a9de18125/docs/sticky.vsd -------------------------------------------------------------------------------- /ngx_http_sticky_misc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net) 3 | */ 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ngx_http_sticky_misc.h" 14 | 15 | #ifndef ngx_str_set 16 | #define ngx_str_set(str, text) (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text 17 | #endif 18 | 19 | /* - fix for 1.11.2 removes include in ngx_md5.h */ 20 | #define MD5_CBLOCK 64 21 | #define MD5_LBLOCK (MD5_CBLOCK/4) 22 | #define MD5_DIGEST_LENGTH 16 23 | #define SHA_CBLOCK 64 24 | #define SHA_DIGEST_LENGTH 20 25 | 26 | #ifndef SHA_DIGEST_LENGTH 27 | #define SHA_CBLOCK 64 28 | #define SHA_DIGEST_LENGTH 20 29 | #endif 30 | 31 | // /* - bugfix for compiling on sles11 - needs gcc4.6 or later*/ 32 | // #pragma GCC diagnostic ignored "-Wuninitialized" 33 | 34 | static ngx_int_t cookie_expires(char *str, size_t size, time_t t) 35 | { 36 | char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 37 | char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 38 | struct tm e; 39 | gmtime_r(&t, &e); 40 | return snprintf(str, size, "%s, %02d-%s-%04d %02d:%02d:%02d GMT", 41 | wdays[e.tm_wday], e.tm_mday, months[e.tm_mon], e.tm_year + 1900, e.tm_hour,e.tm_min,e.tm_sec); 42 | } 43 | 44 | 45 | ngx_int_t ngx_http_sticky_misc_set_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires, unsigned secure, unsigned httponly) 46 | { 47 | u_char *cookie, *p; 48 | size_t len; 49 | ngx_table_elt_t *set_cookie; 50 | ngx_str_t remove; 51 | char expires_str[80]; 52 | 53 | int expires_len = 0; 54 | 55 | if (value == NULL) { 56 | ngx_str_set(&remove, "_remove_"); 57 | value = &remove; 58 | } 59 | 60 | /* name = value */ 61 | len = name->len + 1 + value->len; 62 | 63 | /*; Domain= */ 64 | if (domain->len > 0) { 65 | len += sizeof("; Domain=") - 1 + domain->len; 66 | } 67 | /*; Expires= */ 68 | if (expires != NGX_CONF_UNSET) { 69 | expires_len = cookie_expires(expires_str, sizeof(expires_str), time(NULL) + expires); 70 | len += sizeof("; Expires=") - 1 + expires_len; 71 | } 72 | 73 | /* ; Path= */ 74 | if (path->len > 0) { 75 | len += sizeof("; Path=") - 1 + path->len; 76 | } 77 | 78 | /* ; Secure */ 79 | if (secure) { 80 | len += sizeof("; Secure") - 1; 81 | } 82 | 83 | /* ; HttpOnly */ 84 | if (httponly) { 85 | len += sizeof("; HttpOnly") - 1; 86 | } 87 | 88 | cookie = ngx_pnalloc(r->pool, len); 89 | if (cookie == NULL) { 90 | return NGX_ERROR; 91 | } 92 | 93 | p = ngx_copy(cookie, name->data, name->len); 94 | *p++ = '='; 95 | p = ngx_copy(p, value->data, value->len); 96 | 97 | if (domain->len > 0) { 98 | p = ngx_copy(p, "; Domain=", sizeof("; Domain=") - 1); 99 | p = ngx_copy(p, domain->data, domain->len); 100 | } 101 | 102 | if (expires != NGX_CONF_UNSET) { 103 | p = ngx_copy(p, "; Expires=", sizeof("; Expires=") - 1); 104 | p = ngx_copy(p, expires_str, expires_len); 105 | } 106 | 107 | if (path->len > 0) { 108 | p = ngx_copy(p, "; Path=", sizeof("; Path=") - 1); 109 | p = ngx_copy(p, path->data, path->len); 110 | } 111 | 112 | if (secure) { 113 | p = ngx_copy(p, "; Secure", sizeof("; Secure") - 1); 114 | } 115 | 116 | if (httponly) { 117 | p = ngx_copy(p, "; HttpOnly", sizeof("; HttpOnly") - 1); 118 | } 119 | 120 | set_cookie = ngx_list_push(&r->headers_out.headers); 121 | if (set_cookie == NULL) { 122 | return NGX_ERROR; 123 | } 124 | set_cookie->hash = 1; 125 | ngx_str_set(&set_cookie->key, "Set-Cookie"); 126 | set_cookie->value.len = p - cookie; 127 | set_cookie->value.data = cookie; 128 | 129 | return NGX_OK; 130 | } 131 | 132 | ngx_int_t ngx_http_sticky_misc_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest) 133 | { 134 | ngx_md5_t md5; 135 | u_char hash[MD5_DIGEST_LENGTH]; 136 | 137 | digest->data = ngx_pcalloc(pool, MD5_DIGEST_LENGTH * 2); 138 | if (digest->data == NULL) { 139 | return NGX_ERROR; 140 | } 141 | 142 | digest->len = MD5_DIGEST_LENGTH * 2; 143 | ngx_md5_init(&md5); 144 | ngx_md5_update(&md5, in, len); 145 | ngx_md5_final(hash, &md5); 146 | 147 | ngx_hex_dump(digest->data, hash, MD5_DIGEST_LENGTH); 148 | return NGX_OK; 149 | } 150 | 151 | ngx_int_t ngx_http_sticky_misc_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest) 152 | { 153 | ngx_sha1_t sha1; 154 | u_char hash[SHA_DIGEST_LENGTH]; 155 | 156 | digest->data = ngx_pcalloc(pool, SHA_DIGEST_LENGTH * 2); 157 | if (digest->data == NULL) { 158 | return NGX_ERROR; 159 | } 160 | 161 | digest->len = SHA_DIGEST_LENGTH * 2; 162 | ngx_sha1_init(&sha1); 163 | ngx_sha1_update(&sha1, in, len); 164 | ngx_sha1_final(hash, &sha1); 165 | 166 | ngx_hex_dump(digest->data, hash, SHA_DIGEST_LENGTH); 167 | return NGX_OK; 168 | } 169 | 170 | ngx_int_t ngx_http_sticky_misc_hmac_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest) 171 | { 172 | u_char hash[MD5_DIGEST_LENGTH]; 173 | u_char k[MD5_CBLOCK]; 174 | ngx_md5_t md5; 175 | u_int i; 176 | 177 | digest->data = ngx_pcalloc(pool, MD5_DIGEST_LENGTH * 2); 178 | if (digest->data == NULL) { 179 | return NGX_ERROR; 180 | } 181 | digest->len = MD5_DIGEST_LENGTH * 2; 182 | 183 | ngx_memzero(k, sizeof(k)); 184 | 185 | if (key->len > MD5_CBLOCK) { 186 | ngx_md5_init(&md5); 187 | ngx_md5_update(&md5, key->data, key->len); 188 | ngx_md5_final(k, &md5); 189 | } else { 190 | ngx_memcpy(k, key->data, key->len); 191 | } 192 | 193 | /* XOR ipad */ 194 | for (i=0; i < MD5_CBLOCK; i++) { 195 | k[i] ^= 0x36; 196 | } 197 | 198 | ngx_md5_init(&md5); 199 | ngx_md5_update(&md5, k, MD5_CBLOCK); 200 | ngx_md5_update(&md5, in, len); 201 | ngx_md5_final(hash, &md5); 202 | 203 | /* Convert k to opad -- 0x6A = 0x36 ^ 0x5C */ 204 | for (i=0; i < MD5_CBLOCK; i++) { 205 | k[i] ^= 0x6a; 206 | } 207 | 208 | ngx_md5_init(&md5); 209 | ngx_md5_update(&md5, k, MD5_CBLOCK); 210 | ngx_md5_update(&md5, hash, MD5_DIGEST_LENGTH); 211 | ngx_md5_final(hash, &md5); 212 | 213 | ngx_hex_dump(digest->data, hash, MD5_DIGEST_LENGTH); 214 | 215 | return NGX_OK; 216 | } 217 | 218 | ngx_int_t ngx_http_sticky_misc_hmac_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest) 219 | { 220 | u_char hash[SHA_DIGEST_LENGTH]; 221 | u_char k[SHA_CBLOCK]; 222 | ngx_sha1_t sha1; 223 | u_int i; 224 | 225 | digest->data = ngx_pcalloc(pool, SHA_DIGEST_LENGTH * 2); 226 | if (digest->data == NULL) { 227 | return NGX_ERROR; 228 | } 229 | digest->len = SHA_DIGEST_LENGTH * 2; 230 | 231 | ngx_memzero(k, sizeof(k)); 232 | 233 | if (key->len > SHA_CBLOCK) { 234 | ngx_sha1_init(&sha1); 235 | ngx_sha1_update(&sha1, key->data, key->len); 236 | ngx_sha1_final(k, &sha1); 237 | } else { 238 | ngx_memcpy(k, key->data, key->len); 239 | } 240 | 241 | /* XOR ipad */ 242 | for (i=0; i < SHA_CBLOCK; i++) { 243 | k[i] ^= 0x36; 244 | } 245 | 246 | ngx_sha1_init(&sha1); 247 | ngx_sha1_update(&sha1, k, SHA_CBLOCK); 248 | ngx_sha1_update(&sha1, in, len); 249 | ngx_sha1_final(hash, &sha1); 250 | 251 | /* Convert k to opad -- 0x6A = 0x36 ^ 0x5C */ 252 | for (i=0; i < SHA_CBLOCK; i++) { 253 | k[i] ^= 0x6a; 254 | } 255 | 256 | ngx_sha1_init(&sha1); 257 | ngx_sha1_update(&sha1, k, SHA_CBLOCK); 258 | ngx_sha1_update(&sha1, hash, SHA_DIGEST_LENGTH); 259 | ngx_sha1_final(hash, &sha1); 260 | 261 | ngx_hex_dump(digest->data, hash, SHA_DIGEST_LENGTH); 262 | 263 | return NGX_OK; 264 | } 265 | 266 | ngx_int_t ngx_http_sticky_misc_text_raw(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest) 267 | { 268 | if (!in) { 269 | return NGX_ERROR; 270 | } 271 | 272 | digest->data = ngx_pnalloc(pool, len); 273 | if (digest->data == NULL) { 274 | return NGX_ERROR; 275 | } 276 | memcpy(digest->data, in, len); 277 | digest->len = len; 278 | 279 | return NGX_OK; 280 | } 281 | -------------------------------------------------------------------------------- /ngx_http_sticky_misc.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net) 4 | */ 5 | 6 | #ifndef _NGX_HTTP_STICKY_MISC_H_INCLUDED_ 7 | #define _NGX_HTTP_STICKY_MISC_H_INCLUDED_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef ngx_int_t (*ngx_http_sticky_misc_hash_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 15 | typedef ngx_int_t (*ngx_http_sticky_misc_hmac_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest); 16 | typedef ngx_int_t (*ngx_http_sticky_misc_text_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 17 | 18 | ngx_int_t ngx_http_sticky_misc_set_cookie (ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires, unsigned secure, unsigned httponly); 19 | ngx_int_t ngx_http_sticky_misc_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 20 | ngx_int_t ngx_http_sticky_misc_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 21 | ngx_int_t ngx_http_sticky_misc_hmac_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest); 22 | ngx_int_t ngx_http_sticky_misc_hmac_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest); 23 | 24 | ngx_int_t ngx_http_sticky_misc_text_raw(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 25 | 26 | #endif /* _NGX_HTTP_STICKY_MISC_H_INCLUDED_ */ 27 | -------------------------------------------------------------------------------- /ngx_http_sticky_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Jerome Loyet 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ngx_http_sticky_misc.h" 12 | 13 | #if (NGX_UPSTREAM_CHECK_MODULE) 14 | #include "ngx_http_upstream_check_handler.h" 15 | #endif 16 | 17 | 18 | /* define a peer */ 19 | typedef struct { 20 | ngx_http_upstream_rr_peer_t *rr_peer; 21 | ngx_str_t digest; 22 | } ngx_http_sticky_peer_t; 23 | 24 | /* the configuration structure */ 25 | typedef struct { 26 | ngx_http_upstream_srv_conf_t uscf; 27 | ngx_str_t cookie_name; 28 | ngx_str_t cookie_domain; 29 | ngx_str_t cookie_path; 30 | time_t cookie_expires; 31 | unsigned cookie_secure:1; 32 | unsigned cookie_httponly:1; 33 | unsigned transfer_cookie:1; 34 | ngx_str_t transfer_delim; 35 | ngx_str_t hmac_key; 36 | ngx_http_sticky_misc_hash_pt hash; 37 | ngx_http_sticky_misc_hmac_pt hmac; 38 | ngx_http_sticky_misc_text_pt text; 39 | ngx_uint_t no_fallback; 40 | ngx_uint_t hide_cookie; 41 | ngx_http_sticky_peer_t *peers; 42 | } ngx_http_sticky_srv_conf_t; 43 | 44 | 45 | /* the configuration loc structure */ 46 | typedef struct { 47 | ngx_uint_t no_fallback; 48 | ngx_uint_t hide_cookie; 49 | } ngx_http_sticky_loc_conf_t; 50 | 51 | 52 | /* the custom sticky struct used on each request */ 53 | typedef struct { 54 | /* the round robin data must be first */ 55 | ngx_http_upstream_rr_peer_data_t rrp; 56 | ngx_event_get_peer_pt get_rr_peer; 57 | int selected_peer; 58 | ngx_http_sticky_srv_conf_t *sticky_conf; 59 | ngx_http_sticky_loc_conf_t *loc_conf; 60 | ngx_http_request_t *request; 61 | ngx_str_t cookie_route; 62 | } ngx_http_sticky_peer_data_t; 63 | 64 | 65 | static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); 66 | static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data); 67 | static ngx_int_t ngx_http_sticky_header_filter(ngx_http_request_t *r); 68 | static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 69 | static void *ngx_http_sticky_create_conf(ngx_conf_t *cf); 70 | static char *ngx_http_sticky_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 71 | static void *ngx_http_sticky_create_loc_conf(ngx_conf_t *cf); 72 | static char *ngx_conf_set_noargs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 73 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 74 | 75 | static ngx_command_t ngx_http_sticky_commands[] = { 76 | 77 | { ngx_string("sticky"), 78 | NGX_HTTP_UPS_CONF|NGX_CONF_ANY, 79 | ngx_http_sticky_set, 80 | 0, 81 | 0, 82 | NULL }, 83 | 84 | { ngx_string("sticky_no_fallback"), 85 | NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, 86 | ngx_conf_set_noargs_slot, 87 | NGX_HTTP_LOC_CONF_OFFSET, 88 | 0, 89 | NULL }, 90 | 91 | { ngx_string("sticky_hide_cookie"), 92 | NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, 93 | ngx_conf_set_noargs_slot, 94 | NGX_HTTP_LOC_CONF_OFFSET, 95 | offsetof(ngx_http_sticky_loc_conf_t, hide_cookie), 96 | NULL }, 97 | 98 | ngx_null_command 99 | }; 100 | 101 | 102 | static ngx_http_module_t ngx_http_sticky_module_ctx = { 103 | NULL, /* preconfiguration */ 104 | NULL, /* postconfiguration */ 105 | 106 | NULL, /* create main configuration */ 107 | NULL, /* init main configuration */ 108 | 109 | ngx_http_sticky_create_conf, /* create server configuration */ 110 | NULL, /* merge server configuration */ 111 | 112 | ngx_http_sticky_create_loc_conf, /* create location configuration */ 113 | ngx_http_sticky_merge_loc_conf /* merge location configuration */ 114 | }; 115 | 116 | 117 | ngx_module_t ngx_http_sticky_module = { 118 | NGX_MODULE_V1, 119 | &ngx_http_sticky_module_ctx, /* module context */ 120 | ngx_http_sticky_commands, /* module directives */ 121 | NGX_HTTP_MODULE, /* module type */ 122 | NULL, /* init master */ 123 | NULL, /* init module */ 124 | NULL, /* init process */ 125 | NULL, /* init thread */ 126 | NULL, /* exit thread */ 127 | NULL, /* exit process */ 128 | NULL, /* exit master */ 129 | NGX_MODULE_V1_PADDING 130 | }; 131 | 132 | 133 | /* 134 | * function called by the upstream module to init itself 135 | * it's called once per instance 136 | */ 137 | ngx_int_t ngx_http_init_upstream_sticky(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) 138 | { 139 | ngx_http_upstream_rr_peers_t *rr_peers; 140 | ngx_http_sticky_srv_conf_t *conf; 141 | ngx_uint_t i; 142 | 143 | /* call the rr module on wich the sticky module is based on */ 144 | if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { 145 | return NGX_ERROR; 146 | } 147 | 148 | /* calculate each peer digest once and save */ 149 | rr_peers = us->peer.data; 150 | 151 | /* do nothing there's only one peer */ 152 | if (rr_peers->number <= 1 || rr_peers->single) { 153 | return NGX_OK; 154 | } 155 | 156 | /* tell the upstream module to call ngx_http_init_sticky_peer when it inits peer */ 157 | us->peer.init = ngx_http_init_sticky_peer; 158 | 159 | conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module); 160 | 161 | /* if 'index', no need to alloc and generate digest */ 162 | if (!conf->hash && !conf->hmac && !conf->text) { 163 | conf->peers = NULL; 164 | return NGX_OK; 165 | } 166 | 167 | /* create our own upstream indexes */ 168 | conf->peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_peer_t) * rr_peers->number); 169 | if (conf->peers == NULL) { 170 | return NGX_ERROR; 171 | } 172 | 173 | /* parse each peer and generate digest if necessary */ 174 | for (i = 0; i < rr_peers->number; i++) { 175 | conf->peers[i].rr_peer = &rr_peers->peer[i]; 176 | 177 | if (conf->hmac) { 178 | /* generate hmac */ 179 | conf->hmac(cf->pool, rr_peers->peer[i].server.data, rr_peers->peer[i].server.len, &conf->hmac_key, &conf->peers[i].digest); 180 | 181 | } else if (conf->text) { 182 | /* generate text */ 183 | conf->text(cf->pool, rr_peers->peer[i].server.data, rr_peers->peer[i].server.len, &conf->peers[i].digest); 184 | 185 | } else { 186 | /* generate hash */ 187 | conf->hash(cf->pool, rr_peers->peer[i].server.data, rr_peers->peer[i].server.len, &conf->peers[i].digest); 188 | } 189 | 190 | 191 | } 192 | 193 | return NGX_OK; 194 | } 195 | 196 | /* 197 | * function called by the upstream module when it inits each peer 198 | * it's called once per request 199 | */ 200 | static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) 201 | { 202 | ngx_http_sticky_peer_data_t *iphp; 203 | ngx_str_t route; 204 | ngx_uint_t i; 205 | ngx_int_t n; 206 | u_char *p; 207 | 208 | /* alloc custom sticky struct */ 209 | iphp = ngx_palloc(r->pool, sizeof(ngx_http_sticky_peer_data_t)); 210 | if (iphp == NULL) { 211 | return NGX_ERROR; 212 | } 213 | 214 | /* attach it to the request upstream data */ 215 | r->upstream->peer.data = &iphp->rrp; 216 | 217 | /* call the rr module on which the sticky is based on */ 218 | if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { 219 | return NGX_ERROR; 220 | } 221 | 222 | /* set the callback to select the next peer to use */ 223 | r->upstream->peer.get = ngx_http_get_sticky_peer; 224 | 225 | /* HACK: because ngx_http_top_header_filter not defined on postconfiguration step in non filtering module */ 226 | if (ngx_http_top_header_filter != ngx_http_sticky_header_filter) { 227 | ngx_http_next_header_filter = ngx_http_top_header_filter; 228 | ngx_http_top_header_filter = ngx_http_sticky_header_filter; 229 | } 230 | 231 | /* init the custom sticky struct */ 232 | iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; 233 | iphp->selected_peer = -1; 234 | iphp->sticky_conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module); 235 | iphp->loc_conf = ngx_http_get_module_loc_conf(r, ngx_http_sticky_module); 236 | iphp->request = r; 237 | iphp->cookie_route.data = NULL; 238 | iphp->cookie_route.len = 0; 239 | 240 | ngx_http_set_ctx(r, iphp, ngx_http_sticky_module); 241 | 242 | /* check weather a cookie is present or not and save it */ 243 | if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &iphp->sticky_conf->cookie_name, &route) != NGX_DECLINED) { 244 | /* a route cookie has been found. Let's give it a try */ 245 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] got cookie route=%V, let's try to find a matching peer", &route); 246 | 247 | /* extract digest from cookie */ 248 | if (iphp->sticky_conf->transfer_cookie) { 249 | p = ngx_strnstr(route.data, (char *)iphp->sticky_conf->transfer_delim.data, route.len); 250 | if (p != NULL) { 251 | route.len = p - route.data; 252 | } 253 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] extract route \"%V\"", &route); 254 | 255 | /* TODO: modify r->upstream->request_bufs->buf, see ngx_http_proxy_create_request(r) */ 256 | } 257 | 258 | iphp->cookie_route.data = route.data; 259 | iphp->cookie_route.len = route.len; 260 | 261 | /* hash, hmac or text, just compare digest */ 262 | if (iphp->sticky_conf->hash || iphp->sticky_conf->hmac || iphp->sticky_conf->text) { 263 | 264 | /* check internal struct has been set */ 265 | if (!iphp->sticky_conf->peers) { 266 | /* log a warning, as it will continue without the sticky */ 267 | ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] internal peers struct has not been set"); 268 | return NGX_OK; /* return OK, in order to continue */ 269 | } 270 | 271 | /* search the digest found in the cookie in the peer digest list */ 272 | for (i = 0; i < iphp->rrp.peers->number; i++) { 273 | 274 | /* ensure the both len are equal and > 0 */ 275 | if (iphp->sticky_conf->peers[i].digest.len != route.len || route.len <= 0) { 276 | continue; 277 | } 278 | 279 | if (!ngx_strncmp(iphp->sticky_conf->peers[i].digest.data, route.data, route.len)) { 280 | /* we found a match */ 281 | iphp->selected_peer = i; 282 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %ui", &route, i); 283 | return NGX_OK; 284 | } 285 | } 286 | 287 | } else { 288 | 289 | /* switch back to index, just convert to integer and ensure it corresponds to a valid peer */ 290 | n = ngx_atoi(route.data, route.len); 291 | if (n == NGX_ERROR) { 292 | ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] unable to convert the route \"%V\" to an integer value", &route); 293 | } else if (n >= 0 && n < (ngx_int_t)iphp->rrp.peers->number) { 294 | /* found one */ 295 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %i", &route, n); 296 | iphp->selected_peer = n; 297 | return NGX_OK; 298 | } 299 | } 300 | 301 | /* nothing was found, just continue with rr */ 302 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" does not match any peer. Just ignoring it ...", &route); 303 | return NGX_OK; 304 | } 305 | 306 | /* nothing found */ 307 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] route cookie not found"); 308 | return NGX_OK; /* return OK, in order to continue */ 309 | } 310 | 311 | /* 312 | * function called by the upstream module to choose the next peer to use 313 | * called at least one time per request 314 | */ 315 | static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data) 316 | { 317 | ngx_http_sticky_peer_data_t *iphp = data; 318 | ngx_http_sticky_srv_conf_t *conf = iphp->sticky_conf; 319 | ngx_http_sticky_loc_conf_t *loc_conf = iphp->loc_conf; 320 | ngx_int_t selected_peer = -1; 321 | time_t now = ngx_time(); 322 | uintptr_t m = 0; 323 | ngx_uint_t n = 0, i; 324 | ngx_http_upstream_rr_peer_t *peer = NULL; 325 | 326 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] get sticky peer, try: %ui, n_peers: %ui, no_fallback: %ui/%ui", pc->tries, iphp->rrp.peers->number, conf->no_fallback, loc_conf->no_fallback); 327 | 328 | /* TODO: cached */ 329 | 330 | /* has the sticky module already choosen a peer to connect to and is it a valid peer */ 331 | /* is there more than one peer (otherwise, no choices to make) */ 332 | if (iphp->selected_peer >= 0 && iphp->selected_peer < (ngx_int_t)iphp->rrp.peers->number && !iphp->rrp.peers->single) { 333 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] let's try the selected peer (%i)", iphp->selected_peer); 334 | 335 | n = iphp->selected_peer / (8 * sizeof(uintptr_t)); 336 | m = (uintptr_t) 1 << iphp->selected_peer % (8 * sizeof(uintptr_t)); 337 | 338 | /* has the peer not already been tried ? */ 339 | if (!(iphp->rrp.tried[n] & m)) { 340 | peer = &iphp->rrp.peers->peer[iphp->selected_peer]; 341 | 342 | /* if the no_fallback flag is set */ 343 | if (conf->no_fallback || loc_conf->no_fallback) { 344 | 345 | /* if peer is down */ 346 | if (peer->down) { 347 | ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is down and no_fallback is flagged"); 348 | return NGX_BUSY; 349 | } 350 | 351 | /* if it's been ignored for long enought (fail_timeout), reset timeout */ 352 | /* do this check before testing peer->fails ! :) */ 353 | if (now - peer->accessed > peer->fail_timeout) { 354 | peer->fails = 0; 355 | } 356 | 357 | /* if peer is failed */ 358 | if (peer->max_fails > 0 && peer->fails >= peer->max_fails) { 359 | ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is maked as failed and no_fallback is flagged"); 360 | return NGX_BUSY; 361 | } 362 | } 363 | 364 | /* ensure the peer is not marked as down */ 365 | if (!peer->down) { 366 | 367 | /* if it's not failedi, use it */ 368 | if (peer->max_fails == 0 || peer->fails < peer->max_fails) { 369 | selected_peer = (ngx_int_t)n; 370 | 371 | /* if it's been ignored for long enought (fail_timeout), reset timeout and use it */ 372 | } else if (now - peer->accessed > peer->fail_timeout) { 373 | peer->fails = 0; 374 | selected_peer = (ngx_int_t)n; 375 | 376 | /* it's failed or timeout did not expire yet */ 377 | } else { 378 | /* mark the peer as tried */ 379 | iphp->rrp.tried[n] |= m; 380 | } 381 | } 382 | } 383 | } 384 | 385 | /* we have a valid peer, tell the upstream module to use it */ 386 | if (peer && selected_peer >= 0) { 387 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] peer found at index %i", selected_peer); 388 | 389 | #if defined(nginx_version) && nginx_version >= 1009000 390 | iphp->rrp.current = peer; 391 | #else 392 | iphp->rrp.current = iphp->selected_peer; 393 | #endif 394 | 395 | pc->cached = 0; 396 | pc->connection = NULL; 397 | pc->sockaddr = peer->sockaddr; 398 | pc->socklen = peer->socklen; 399 | pc->name = &peer->name; 400 | 401 | iphp->rrp.tried[n] |= m; 402 | 403 | } else { 404 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] no sticky peer selected, switch back to classic rr"); 405 | 406 | if (iphp->sticky_conf->no_fallback || iphp->loc_conf->no_fallback) { 407 | ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] No fallback in action !"); 408 | return NGX_BUSY; 409 | } 410 | 411 | ngx_int_t ret = iphp->get_rr_peer(pc, &iphp->rrp); 412 | if (ret != NGX_OK) { 413 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] ngx_http_upstream_get_round_robin_peer returned %i", ret); 414 | return ret; 415 | } 416 | 417 | /* search for the choosen peer in order to set the cookie */ 418 | for (i = 0; i < iphp->rrp.peers->number; i++) { 419 | 420 | if (iphp->rrp.peers->peer[i].sockaddr == pc->sockaddr && iphp->rrp.peers->peer[i].socklen == pc->socklen) { 421 | if (conf->hash || conf->hmac || conf->text) { 422 | iphp->cookie_route.data = ngx_pnalloc(iphp->request->pool, conf->peers[i].digest.len + 1); 423 | if (iphp->cookie_route.data == NULL) { 424 | return NGX_ERROR; 425 | } 426 | (void) ngx_cpystrn(iphp->cookie_route.data, conf->peers[i].digest.data, conf->peers[i].digest.len + 1); 427 | iphp->cookie_route.len = conf->peers[i].digest.len; 428 | } else { 429 | ngx_uint_t tmp = i; 430 | iphp->cookie_route.len = 0; 431 | do { 432 | iphp->cookie_route.len++; 433 | } while (tmp /= 10); 434 | iphp->cookie_route.data = ngx_pcalloc(iphp->request->pool, sizeof(u_char) * (iphp->cookie_route.len + 1)); 435 | if (iphp->cookie_route.data == NULL) { 436 | break; 437 | } 438 | ngx_snprintf(iphp->cookie_route.data, iphp->cookie_route.len, "%d", i); 439 | iphp->cookie_route.len = ngx_strlen(iphp->cookie_route.data); 440 | } 441 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] preset route to set the cookie \"%V\" value=\"%V\" index=%ui", 442 | &conf->cookie_name, &iphp->cookie_route, i); 443 | 444 | break; /* found and hopefully the cookie will be set */ 445 | } 446 | } 447 | } 448 | 449 | /* reset the selection in order to bypass the sticky module when the upstream module will try another peers if necessary */ 450 | iphp->selected_peer = -1; 451 | 452 | return NGX_OK; 453 | } 454 | 455 | /* 456 | * Function called when a handler generates a response 457 | */ 458 | static ngx_int_t ngx_http_sticky_header_filter(ngx_http_request_t *r) 459 | { 460 | ngx_http_sticky_peer_data_t *ctx; 461 | ngx_list_part_t *part; 462 | ngx_table_elt_t *elt; 463 | ngx_uint_t i; 464 | ngx_str_t transfer_cookie; 465 | ngx_str_t result_cookie; 466 | u_char *p; 467 | size_t len; 468 | 469 | ctx = ngx_http_get_module_ctx(r, ngx_http_sticky_module); 470 | if (ctx == NULL || ctx->cookie_route.data == NULL) { 471 | return ngx_http_next_header_filter(r); 472 | } 473 | 474 | if (ctx->sticky_conf->transfer_cookie) { 475 | if (ngx_http_parse_set_cookie_lines(&r->upstream->headers_in.cookies, &ctx->sticky_conf->cookie_name, &transfer_cookie) == NGX_DECLINED) 476 | { 477 | ngx_str_null(&transfer_cookie); 478 | } 479 | } 480 | 481 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/ngx_http_sticky_header_filter] clean Set-Cookie with some name"); 482 | part = &r->headers_out.headers.part; 483 | elt = part->elts; 484 | for (i = 0; /* void */; i++) { 485 | if (i >= part->nelts) { 486 | if (part->next == NULL) { 487 | break; 488 | } 489 | part = part->next; 490 | elt = part->elts; 491 | i = 0; 492 | } 493 | 494 | if (ngx_strncasecmp(elt[i].key.data, (u_char *)"set-cookie", 10) == 0 495 | && ngx_strncasecmp(elt[i].value.data, ctx->sticky_conf->cookie_name.data, ctx->sticky_conf->cookie_name.len) == 0 496 | && elt[i].value.data[ctx->sticky_conf->cookie_name.len] == '=') { 497 | elt[i].hash = 0; 498 | } 499 | } 500 | 501 | if (!ctx->sticky_conf->hide_cookie && !ctx->loc_conf->hide_cookie) { 502 | if (ctx->sticky_conf->transfer_cookie && transfer_cookie.len != 0) { 503 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/ngx_http_sticky_header_filter] add transfer cookie"); 504 | len = ctx->cookie_route.len + ctx->sticky_conf->transfer_delim.len + transfer_cookie.len; 505 | result_cookie.data = ngx_palloc(r->pool, len); 506 | if (result_cookie.data == NULL) { 507 | return NGX_ERROR; 508 | } 509 | p = ngx_copy(result_cookie.data, ctx->cookie_route.data, ctx->cookie_route.len); 510 | p = ngx_copy(p, ctx->sticky_conf->transfer_delim.data, ctx->sticky_conf->transfer_delim.len); 511 | (void) ngx_copy(p, transfer_cookie.data, transfer_cookie.len); 512 | result_cookie.len = len; 513 | } else { 514 | result_cookie = ctx->cookie_route; 515 | } 516 | 517 | ngx_http_sticky_misc_set_cookie(r, &ctx->sticky_conf->cookie_name, &result_cookie, &ctx->sticky_conf->cookie_domain, 518 | &ctx->sticky_conf->cookie_path, ctx->sticky_conf->cookie_expires, ctx->sticky_conf->cookie_secure, ctx->sticky_conf->cookie_httponly); 519 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/ngx_http_sticky_header_filter] set cookie \"%V\" value=\"%V\"", 520 | &ctx->sticky_conf->cookie_name, &result_cookie); 521 | } 522 | 523 | return ngx_http_next_header_filter(r); 524 | } 525 | 526 | /* 527 | * Function called when the sticky command is parsed on the conf file 528 | */ 529 | static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 530 | { 531 | ngx_http_upstream_srv_conf_t *upstream_conf; 532 | ngx_http_sticky_srv_conf_t *sticky_conf; 533 | ngx_uint_t i; 534 | ngx_str_t tmp; 535 | ngx_str_t name = ngx_string("route"); 536 | ngx_str_t domain = ngx_string(""); 537 | ngx_str_t path = ngx_string("/"); 538 | ngx_str_t hmac_key = ngx_string(""); 539 | ngx_str_t delimiter = ngx_string(" "); 540 | time_t expires = NGX_CONF_UNSET; 541 | unsigned secure = 0; 542 | unsigned httponly = 0; 543 | unsigned transfer = 0; 544 | ngx_http_sticky_misc_hash_pt hash = NGX_CONF_UNSET_PTR; 545 | ngx_http_sticky_misc_hmac_pt hmac = NULL; 546 | ngx_http_sticky_misc_text_pt text = NULL; 547 | ngx_uint_t no_fallback = 0; 548 | ngx_uint_t hide_cookie = 0; 549 | 550 | /* parse all elements */ 551 | for (i = 1; i < cf->args->nelts; i++) { 552 | ngx_str_t *value = cf->args->elts; 553 | 554 | /* is "name=" is starting the argument ? */ 555 | if ((u_char *)ngx_strstr(value[i].data, "name=") == value[i].data) { 556 | 557 | /* do we have at least one char after "name=" ? */ 558 | if (value[i].len <= sizeof("name=") - 1) { 559 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"name=\""); 560 | return NGX_CONF_ERROR; 561 | } 562 | 563 | /* save what's after "name=" */ 564 | name.len = value[i].len - ngx_strlen("name="); 565 | name.data = (u_char *)(value[i].data + sizeof("name=") - 1); 566 | continue; 567 | } 568 | 569 | /* is "domain=" is starting the argument ? */ 570 | if ((u_char *)ngx_strstr(value[i].data, "domain=") == value[i].data) { 571 | 572 | /* do we have at least one char after "domain=" ? */ 573 | if (value[i].len <= ngx_strlen("domain=")) { 574 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"domain=\""); 575 | return NGX_CONF_ERROR; 576 | } 577 | 578 | /* save what's after "domain=" */ 579 | domain.len = value[i].len - ngx_strlen("domain="); 580 | domain.data = (u_char *)(value[i].data + sizeof("domain=") - 1); 581 | continue; 582 | } 583 | 584 | /* is "path=" is starting the argument ? */ 585 | if ((u_char *)ngx_strstr(value[i].data, "path=") == value[i].data) { 586 | 587 | /* do we have at least one char after "path=" ? */ 588 | if (value[i].len <= ngx_strlen("path=")) { 589 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"path=\""); 590 | return NGX_CONF_ERROR; 591 | } 592 | 593 | /* save what's after "path=" */ 594 | path.len = value[i].len - ngx_strlen("path="); 595 | path.data = (u_char *)(value[i].data + sizeof("path=") - 1); 596 | continue; 597 | } 598 | 599 | /* is "expires=" is starting the argument ? */ 600 | if ((u_char *)ngx_strstr(value[i].data, "expires=") == value[i].data) { 601 | 602 | /* do we have at least one char after "expires=" ? */ 603 | if (value[i].len <= sizeof("expires=") - 1) { 604 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"expires=\""); 605 | return NGX_CONF_ERROR; 606 | } 607 | 608 | /* extract value */ 609 | tmp.len = value[i].len - ngx_strlen("expires="); 610 | tmp.data = (u_char *)(value[i].data + sizeof("expires=") - 1); 611 | 612 | /* convert to time, save and validate */ 613 | expires = ngx_parse_time(&tmp, 1); 614 | if (expires == NGX_ERROR || expires < 1) { 615 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value for \"expires=\""); 616 | return NGX_CONF_ERROR; 617 | } 618 | continue; 619 | } 620 | 621 | if (ngx_strncmp(value[i].data, "secure", 6) == 0 && value[i].len == 6) { 622 | secure = 1; 623 | continue; 624 | } 625 | 626 | if (ngx_strncmp(value[i].data, "httponly", 8) == 0 && value[i].len == 8) { 627 | httponly = 1; 628 | continue; 629 | } 630 | 631 | if (ngx_strncmp(value[i].data, "transfer", 8) == 0 && value[i].len == 8) { 632 | transfer = 1; 633 | continue; 634 | } 635 | 636 | /* is "delimiter=" is starting the argument ? */ 637 | if ((u_char *)ngx_strstr(value[i].data, "delimiter=") == value[i].data) { 638 | 639 | /* do we have at least one char after "delimiter=" ? */ 640 | if (value[i].len <= ngx_strlen("delimiter=")) { 641 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"delimiter=\""); 642 | return NGX_CONF_ERROR; 643 | } 644 | 645 | /* save what's after "delimiter=" */ 646 | delimiter.len = value[i].len - ngx_strlen("delimiter="); 647 | delimiter.data = ngx_pcalloc(cf->pool, delimiter.len + 1); 648 | if (delimiter.data == NULL) { 649 | return NGX_CONF_ERROR; 650 | } 651 | ngx_memcpy(delimiter.data, (u_char *)(value[i].data + sizeof("delimiter=") - 1), delimiter.len); 652 | delimiter.data[delimiter.len] = '\0'; 653 | continue; 654 | } 655 | 656 | /* is "text=" is starting the argument ? */ 657 | if ((u_char *)ngx_strstr(value[i].data, "text=") == value[i].data) { 658 | 659 | /* only hash or hmac can be used, not both */ 660 | if (hmac || hash != NGX_CONF_UNSET_PTR) { 661 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text=\""); 662 | return NGX_CONF_ERROR; 663 | } 664 | 665 | /* do we have at least one char after "name=" ? */ 666 | if (value[i].len <= sizeof("text=") - 1) { 667 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"text=\""); 668 | return NGX_CONF_ERROR; 669 | } 670 | 671 | /* extract value to temp */ 672 | tmp.len = value[i].len - ngx_strlen("text="); 673 | tmp.data = (u_char *)(value[i].data + sizeof("text=") - 1); 674 | 675 | /* is name=raw */ 676 | if (ngx_strncmp(tmp.data, "raw", sizeof("raw") - 1) == 0 ) { 677 | text = ngx_http_sticky_misc_text_raw; 678 | continue; 679 | } 680 | 681 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"text=\": raw"); 682 | return NGX_CONF_ERROR; 683 | } 684 | 685 | /* is "hash=" is starting the argument ? */ 686 | if ((u_char *)ngx_strstr(value[i].data, "hash=") == value[i].data) { 687 | 688 | /* only hash or hmac can be used, not both */ 689 | if (hmac || text) { 690 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text=\""); 691 | return NGX_CONF_ERROR; 692 | } 693 | 694 | /* do we have at least one char after "hash=" ? */ 695 | if (value[i].len <= sizeof("hash=") - 1) { 696 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hash=\""); 697 | return NGX_CONF_ERROR; 698 | } 699 | 700 | /* extract value to temp */ 701 | tmp.len = value[i].len - ngx_strlen("hash="); 702 | tmp.data = (u_char *)(value[i].data + sizeof("hash=") - 1); 703 | 704 | /* is hash=index */ 705 | if (ngx_strncmp(tmp.data, "index", sizeof("index") - 1) == 0 ) { 706 | hash = NULL; 707 | continue; 708 | } 709 | 710 | /* is hash=md5 */ 711 | if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) { 712 | hash = ngx_http_sticky_misc_md5; 713 | continue; 714 | } 715 | 716 | /* is hash=sha1 */ 717 | if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) { 718 | hash = ngx_http_sticky_misc_sha1; 719 | continue; 720 | } 721 | 722 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hash=\": index, md5 or sha1"); 723 | return NGX_CONF_ERROR; 724 | } 725 | 726 | /* is "hmac=" is starting the argument ? */ 727 | if ((u_char *)ngx_strstr(value[i].data, "hmac=") == value[i].data) { 728 | 729 | /* only hash or hmac can be used, not both */ 730 | if (hash != NGX_CONF_UNSET_PTR || text) { 731 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text\""); 732 | return NGX_CONF_ERROR; 733 | } 734 | 735 | /* do we have at least one char after "hmac=" ? */ 736 | if (value[i].len <= sizeof("hmac=") - 1) { 737 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac=\""); 738 | return NGX_CONF_ERROR; 739 | } 740 | 741 | /* extract value */ 742 | tmp.len = value[i].len - ngx_strlen("hmac="); 743 | tmp.data = (u_char *)(value[i].data + sizeof("hmac=") - 1); 744 | 745 | /* is hmac=md5 ? */ 746 | if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) { 747 | hmac = ngx_http_sticky_misc_hmac_md5; 748 | continue; 749 | } 750 | 751 | /* is hmac=sha1 ? */ 752 | if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) { 753 | hmac = ngx_http_sticky_misc_hmac_sha1; 754 | continue; 755 | } 756 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hmac=\": md5 or sha1"); 757 | return NGX_CONF_ERROR; 758 | } 759 | 760 | /* is "hmac_key=" is starting the argument ? */ 761 | if ((u_char *)ngx_strstr(value[i].data, "hmac_key=") == value[i].data) { 762 | 763 | /* do we have at least one char after "hmac_key=" ? */ 764 | if (value[i].len <= ngx_strlen("hmac_key=")) { 765 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac_key=\""); 766 | return NGX_CONF_ERROR; 767 | } 768 | 769 | /* save what's after "hmac_key=" */ 770 | hmac_key.len = value[i].len - ngx_strlen("hmac_key="); 771 | hmac_key.data = (u_char *)(value[i].data + sizeof("hmac_key=") - 1); 772 | continue; 773 | } 774 | 775 | /* is "no_fallback" flag present ? */ 776 | if (ngx_strncmp(value[i].data, "no_fallback", sizeof("no_fallback") - 1) == 0 ) { 777 | no_fallback = 1; 778 | continue; 779 | } 780 | 781 | /* is "hide_cookie" flag present ? */ 782 | if (ngx_strncmp(value[i].data, "hide_cookie", sizeof("hide_cookie") - 1) == 0 ) { 783 | hide_cookie = 1; 784 | continue; 785 | } 786 | 787 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid arguement (%V)", &value[i]); 788 | return NGX_CONF_ERROR; 789 | } 790 | 791 | /* if has and hmac and name have not been set, default to md5 */ 792 | if (hash == NGX_CONF_UNSET_PTR && hmac == NULL && text == NULL) { 793 | hash = ngx_http_sticky_misc_md5; 794 | } 795 | 796 | /* don't allow meaning less parameters */ 797 | if (hmac_key.len > 0 && hash != NGX_CONF_UNSET_PTR) { 798 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"hmac_key=\" is meaningless when \"hmac\" is used. Please remove it."); 799 | return NGX_CONF_ERROR; 800 | } 801 | 802 | /* ensure we have an hmac key if hmac's been set */ 803 | if (hmac_key.len == 0 && hmac != NULL) { 804 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please specify \"hmac_key=\" when using \"hmac\""); 805 | return NGX_CONF_ERROR; 806 | } 807 | 808 | /* ensure hash is NULL to avoid conflicts later */ 809 | if (hash == NGX_CONF_UNSET_PTR) { 810 | hash = NULL; 811 | } 812 | 813 | /* save the sticky parameters */ 814 | sticky_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_sticky_module); 815 | sticky_conf->cookie_name = name; 816 | sticky_conf->cookie_domain = domain; 817 | sticky_conf->cookie_path = path; 818 | sticky_conf->cookie_expires = expires; 819 | sticky_conf->cookie_secure = secure; 820 | sticky_conf->cookie_httponly = httponly; 821 | sticky_conf->transfer_cookie = transfer; 822 | sticky_conf->transfer_delim = delimiter; 823 | sticky_conf->hash = hash; 824 | sticky_conf->hmac = hmac; 825 | sticky_conf->text = text; 826 | sticky_conf->hmac_key = hmac_key; 827 | sticky_conf->no_fallback = no_fallback; 828 | sticky_conf->hide_cookie = hide_cookie; 829 | sticky_conf->peers = NULL; /* ensure it's null before running */ 830 | 831 | upstream_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); 832 | 833 | /* 834 | * ensure another upstream module has not been already loaded 835 | * peer.init_upstream is set to null and the upstream module use RR if not set 836 | * But this check only works when the other module is declared before sticky 837 | */ 838 | if (upstream_conf->peer.init_upstream) { 839 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "You can't use sticky with another upstream module"); 840 | return NGX_CONF_ERROR; 841 | } 842 | 843 | /* configure the upstream to get back to this module */ 844 | upstream_conf->peer.init_upstream = ngx_http_init_upstream_sticky; 845 | 846 | upstream_conf->flags = NGX_HTTP_UPSTREAM_CREATE 847 | | NGX_HTTP_UPSTREAM_MAX_FAILS 848 | | NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 849 | | NGX_HTTP_UPSTREAM_DOWN 850 | | NGX_HTTP_UPSTREAM_WEIGHT; 851 | 852 | return NGX_CONF_OK; 853 | } 854 | 855 | /* 856 | * alloc stick configuration 857 | */ 858 | static void *ngx_http_sticky_create_conf(ngx_conf_t *cf) 859 | { 860 | ngx_http_sticky_srv_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_srv_conf_t)); 861 | if (conf == NULL) { 862 | return NGX_CONF_ERROR; 863 | } 864 | 865 | return conf; 866 | } 867 | 868 | static void *ngx_http_sticky_create_loc_conf(ngx_conf_t *cf) 869 | { 870 | ngx_http_sticky_loc_conf_t *conf; 871 | 872 | conf = ngx_pcalloc(cf->pool, sizeof(*conf)); 873 | if (conf == NULL) { 874 | return NGX_CONF_ERROR; 875 | } 876 | conf->no_fallback = NGX_CONF_UNSET_UINT; 877 | conf->hide_cookie = NGX_CONF_UNSET_UINT; 878 | return conf; 879 | } 880 | 881 | static char *ngx_http_sticky_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 882 | { 883 | ngx_http_sticky_loc_conf_t *prev = parent; 884 | ngx_http_sticky_loc_conf_t *conf = child; 885 | 886 | ngx_conf_merge_uint_value(conf->no_fallback, prev->no_fallback, 0); 887 | ngx_conf_merge_uint_value(conf->hide_cookie, prev->hide_cookie, 0); 888 | 889 | if (conf->no_fallback > 1) { 890 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 891 | "no_fallback must be equal either to 0 or 1"); 892 | return NGX_CONF_ERROR; 893 | } 894 | if (conf->hide_cookie > 1) { 895 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 896 | "hide_cookie must be equal either to 0 or 1"); 897 | return NGX_CONF_ERROR; 898 | } 899 | return NGX_CONF_OK; 900 | } 901 | 902 | static char *ngx_conf_set_noargs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 903 | { 904 | char *p = conf; 905 | 906 | ngx_uint_t *fp; 907 | ngx_conf_post_t *post; 908 | 909 | fp = (ngx_uint_t *) (p + cmd->offset); 910 | 911 | if (*fp != NGX_CONF_UNSET_UINT) { 912 | return "is duplicate"; 913 | } 914 | 915 | *fp = 1; 916 | 917 | if (cmd->post) { 918 | post = cmd->post; 919 | return post->post_handler(cf, post, fp); 920 | } 921 | 922 | return NGX_CONF_OK; 923 | } 924 | -------------------------------------------------------------------------------- /patches/cookies.patch: -------------------------------------------------------------------------------- 1 | Index: ngx_http_sticky_misc.h 2 | =================================================================== 3 | --- ngx_http_sticky_misc.h (revision 49) 4 | +++ ngx_http_sticky_misc.h (working copy) 5 | @@ -15,7 +15,7 @@ 6 | typedef ngx_int_t (*ngx_http_sticky_misc_hmac_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest); 7 | typedef ngx_int_t (*ngx_http_sticky_misc_text_pt)(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest); 8 | 9 | -ngx_int_t ngx_http_sticky_misc_set_cookie (ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires); 10 | +ngx_int_t ngx_http_sticky_misc_set_cookie (ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires, unsigned secure, unsigned httponly); 11 | ngx_int_t ngx_http_sticky_misc_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 12 | ngx_int_t ngx_http_sticky_misc_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest); 13 | ngx_int_t ngx_http_sticky_misc_hmac_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest); 14 | Index: t/basic.t 15 | =================================================================== 16 | --- t/basic.t (revision 0) 17 | +++ t/basic.t (revision 0) 18 | @@ -0,0 +1,350 @@ 19 | +# vi:filetype= 20 | + 21 | +use lib 'lib'; 22 | +use Test::Nginx::Socket; # 'no_plan'; 23 | +use URI::Escape; 24 | + 25 | +repeat_each(1); 26 | + 27 | +plan tests => repeat_each() * 2 * blocks(); 28 | + 29 | +run_tests(); 30 | + 31 | +__DATA__ 32 | + 33 | +=== TEST 1: hash=md5 34 | +--- http_config 35 | + upstream backend { 36 | + server localhost:$TEST_NGINX_SERVER_PORT; 37 | + server 127.0.0.2:80; 38 | + server 127.0.0.3:80; 39 | + server 127.0.0.4:80; 40 | + server 127.0.0.5:80; 41 | + sticky name=route hash=md5; 42 | + } 43 | +--- config 44 | + location /backend { 45 | + rewrite /backend /frontend break; 46 | + proxy_pass http://backend; 47 | + proxy_set_header Host $host; 48 | + } 49 | + location /frontend { 50 | + echo -n $echo_client_request_headers; 51 | + } 52 | +--- request 53 | +GET /backend 54 | +--- response_headers 55 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92 56 | + 57 | +=== TEST 2: hash=sha1 58 | +--- http_config 59 | + upstream backend { 60 | + server localhost:$TEST_NGINX_SERVER_PORT; 61 | + server 127.0.0.2:80; 62 | + server 127.0.0.3:80; 63 | + server 127.0.0.4:80; 64 | + server 127.0.0.5:80; 65 | + sticky name=route hash=sha1; 66 | + } 67 | +--- config 68 | + location /backend { 69 | + rewrite /backend /frontend break; 70 | + proxy_pass http://backend; 71 | + proxy_set_header Host $host; 72 | + } 73 | + location /frontend { 74 | + echo -n $echo_client_request_headers; 75 | + } 76 | +--- request 77 | +GET /backend 78 | +--- response_headers 79 | +Set-Cookie: route=4262de333a18749d31416a617184734678797276 80 | + 81 | +=== TEST 3: hmac=md5 82 | +--- http_config 83 | + upstream backend { 84 | + server localhost:$TEST_NGINX_SERVER_PORT; 85 | + server 127.0.0.2:80; 86 | + server 127.0.0.3:80; 87 | + server 127.0.0.4:80; 88 | + server 127.0.0.5:80; 89 | + sticky name=route hmac=md5 hmac_key=secret; 90 | + } 91 | +--- config 92 | + location /backend { 93 | + rewrite /backend /frontend break; 94 | + proxy_pass http://backend; 95 | + proxy_set_header Host $host; 96 | + } 97 | + location /frontend { 98 | + echo -n $echo_client_request_headers; 99 | + } 100 | +--- request 101 | +GET /backend 102 | +--- response_headers 103 | +Set-Cookie: route=d20fd0a9eb6864058781ed6104e4c9fd 104 | + 105 | +=== TEST 4: hmac=sha1 106 | +--- http_config 107 | + upstream backend { 108 | + server localhost:$TEST_NGINX_SERVER_PORT; 109 | + server 127.0.0.2:80; 110 | + server 127.0.0.3:80; 111 | + server 127.0.0.4:80; 112 | + server 127.0.0.5:80; 113 | + sticky name=route hmac=sha1 hmac_key=secret; 114 | + } 115 | +--- config 116 | + location /backend { 117 | + rewrite /backend /frontend break; 118 | + proxy_pass http://backend; 119 | + proxy_set_header Host $host; 120 | + } 121 | + location /frontend { 122 | + echo -n $echo_client_request_headers; 123 | + } 124 | +--- request 125 | +GET /backend 126 | +--- response_headers 127 | +Set-Cookie: route=34734c8d4b451151897b62db281c0b055e035adc 128 | + 129 | +=== TEST 5: hmac=sha1 hmac_key=secret2 130 | +--- http_config 131 | + upstream backend { 132 | + server localhost:$TEST_NGINX_SERVER_PORT; 133 | + server 127.0.0.2:80; 134 | + server 127.0.0.3:80; 135 | + server 127.0.0.4:80; 136 | + server 127.0.0.5:80; 137 | + sticky name=route hmac=sha1 hmac_key=secret2; 138 | + } 139 | +--- config 140 | + location /backend { 141 | + rewrite /backend /frontend break; 142 | + proxy_pass http://backend; 143 | + proxy_set_header Host $host; 144 | + } 145 | + location /frontend { 146 | + echo -n $echo_client_request_headers; 147 | + } 148 | +--- request 149 | +GET /backend 150 | +--- response_headers 151 | +Set-Cookie: route=959bfc3973750a198925a3aedf9570f5d9b7e6f8 152 | + 153 | +=== TEST 6: domain=.example.com 154 | +--- http_config 155 | + upstream backend { 156 | + server localhost:$TEST_NGINX_SERVER_PORT; 157 | + server 127.0.0.2:80; 158 | + server 127.0.0.3:80; 159 | + server 127.0.0.4:80; 160 | + server 127.0.0.5:80; 161 | + sticky domain=.example.com; 162 | + } 163 | +--- config 164 | + location /backend { 165 | + rewrite /backend /frontend break; 166 | + proxy_pass http://backend; 167 | + proxy_set_header Host $host; 168 | + } 169 | + location /frontend { 170 | + echo -n $echo_client_request_headers; 171 | + } 172 | +--- request 173 | +GET /backend 174 | +--- response_headers 175 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Domain=.example.com 176 | + 177 | +=== TEST 7: path=/example 178 | +--- http_config 179 | + upstream backend { 180 | + server localhost:$TEST_NGINX_SERVER_PORT; 181 | + server 127.0.0.2:80; 182 | + server 127.0.0.3:80; 183 | + server 127.0.0.4:80; 184 | + server 127.0.0.5:80; 185 | + sticky path=/example; 186 | + } 187 | +--- config 188 | + location /backend { 189 | + rewrite /backend /frontend break; 190 | + proxy_pass http://backend; 191 | + proxy_set_header Host $host; 192 | + } 193 | + location /frontend { 194 | + echo -n $echo_client_request_headers; 195 | + } 196 | +--- request 197 | +GET /backend 198 | +--- response_headers 199 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Path=/example 200 | + 201 | +=== TEST 8: expires=1h 202 | +--- http_config 203 | + upstream backend { 204 | + server localhost:$TEST_NGINX_SERVER_PORT; 205 | + server 127.0.0.2:80; 206 | + server 127.0.0.3:80; 207 | + server 127.0.0.4:80; 208 | + server 127.0.0.5:80; 209 | + sticky expires=1h; 210 | + } 211 | +--- config 212 | + location /backend { 213 | + rewrite /backend /frontend break; 214 | + proxy_pass http://backend; 215 | + proxy_set_header Host $host; 216 | + } 217 | + location /frontend { 218 | + echo -n $echo_client_request_headers; 219 | + } 220 | +--- request 221 | +GET /backend 222 | +--- response_headers 223 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Max-Age=3600 224 | + 225 | +=== TEST 9: text=md5 226 | +--- http_config 227 | + upstream backend { 228 | + server localhost:$TEST_NGINX_SERVER_PORT; 229 | + server 127.0.0.2:80; 230 | + server 127.0.0.3:80; 231 | + server 127.0.0.4:80; 232 | + server 127.0.0.5:80; 233 | + sticky name=route text=md5; 234 | + } 235 | +--- config 236 | + location /backend { 237 | + rewrite /backend /frontend break; 238 | + proxy_pass http://backend; 239 | + proxy_set_header Host $host; 240 | + } 241 | + location /frontend { 242 | + echo -n $echo_client_request_headers; 243 | + } 244 | +--- request 245 | +GET /backend 246 | +--- response_headers 247 | +Set-Cookie: route=f6082e846954099610d58161bf189f37 248 | + 249 | +=== TEST 10: text=sha1 250 | +--- http_config 251 | + upstream backend { 252 | + server localhost:$TEST_NGINX_SERVER_PORT; 253 | + server 127.0.0.2:80; 254 | + server 127.0.0.3:80; 255 | + server 127.0.0.4:80; 256 | + server 127.0.0.5:80; 257 | + sticky name=route text=sha1; 258 | + } 259 | +--- config 260 | + location /backend { 261 | + rewrite /backend /frontend break; 262 | + proxy_pass http://backend; 263 | + proxy_set_header Host $host; 264 | + } 265 | + location /frontend { 266 | + echo -n $echo_client_request_headers; 267 | + } 268 | +--- request 269 | +GET /backend 270 | +--- response_headers 271 | +Set-Cookie: route=17305d40bf37f65329da1850efddd32840891e32 272 | + 273 | +=== TEST 11: text=raw 274 | +--- http_config 275 | + upstream backend { 276 | + server localhost:$TEST_NGINX_SERVER_PORT; 277 | + server 127.0.0.2:80; 278 | + server 127.0.0.3:80; 279 | + server 127.0.0.4:80; 280 | + server 127.0.0.5:80; 281 | + sticky name=route text=raw; 282 | + } 283 | +--- config 284 | + location /backend { 285 | + rewrite /backend /frontend break; 286 | + proxy_pass http://backend; 287 | + proxy_set_header Host $host; 288 | + } 289 | + location /frontend { 290 | + echo -n $echo_client_request_headers; 291 | + } 292 | +--- request 293 | +GET /backend 294 | +--- response_headers 295 | +Set-Cookie: route=127.0.0.1:1984 296 | + 297 | +=== TEST 12: no_fallback 298 | +--- http_config 299 | + upstream backend { 300 | + server localhost:$TEST_NGINX_SERVER_PORT; 301 | + server 127.0.0.2:80; 302 | + server 127.0.0.3:80; 303 | + server 127.0.0.4:80; 304 | + server 127.0.0.5:80; 305 | + sticky no_fallback; 306 | + } 307 | +--- config 308 | + location /backend { 309 | + rewrite /backend /frontend break; 310 | + proxy_pass http://backend; 311 | + proxy_set_header Host $host; 312 | + } 313 | + location /frontend { 314 | + echo -n $echo_client_request_headers; 315 | + } 316 | +--- request 317 | +GET /backend 318 | +--- response_headers 319 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92 320 | + 321 | +=== TEST 13: secure 322 | +--- http_config 323 | + upstream backend { 324 | + server localhost:$TEST_NGINX_SERVER_PORT; 325 | + server 127.0.0.2:80; 326 | + server 127.0.0.3:80; 327 | + server 127.0.0.4:80; 328 | + server 127.0.0.5:80; 329 | + sticky secure; 330 | + } 331 | +--- config 332 | + location /backend { 333 | + rewrite /backend /frontend break; 334 | + proxy_pass http://backend; 335 | + proxy_set_header Host $host; 336 | + } 337 | + location /frontend { 338 | + echo -n $echo_client_request_headers; 339 | + } 340 | +--- request 341 | +GET /backend 342 | +--- response_headers 343 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92; Secure 344 | + 345 | +=== TEST 14: httponly 346 | +--- http_config 347 | + upstream backend { 348 | + server localhost:$TEST_NGINX_SERVER_PORT; 349 | + server 127.0.0.2:80; 350 | + server 127.0.0.3:80; 351 | + server 127.0.0.4:80; 352 | + server 127.0.0.5:80; 353 | + sticky httponly; 354 | + } 355 | +--- config 356 | + location /backend { 357 | + rewrite /backend /frontend break; 358 | + proxy_pass http://backend; 359 | + proxy_set_header Host $host; 360 | + } 361 | + location /frontend { 362 | + echo -n $echo_client_request_headers; 363 | + } 364 | +--- request 365 | +GET /backend 366 | +--- response_headers 367 | +Set-Cookie: route=908c1a9fb15095f454c085282da20d92; HttpOnly 368 | + 369 | Index: ngx_http_sticky_module.c 370 | =================================================================== 371 | --- ngx_http_sticky_module.c (revision 49) 372 | +++ ngx_http_sticky_module.c (working copy) 373 | @@ -23,6 +23,8 @@ 374 | ngx_str_t cookie_domain; 375 | ngx_str_t cookie_path; 376 | time_t cookie_expires; 377 | + unsigned cookie_secure:1; 378 | + unsigned cookie_httponly:1; 379 | ngx_str_t hmac_key; 380 | ngx_http_sticky_misc_hash_pt hash; 381 | ngx_http_sticky_misc_hmac_pt hmac; 382 | @@ -358,7 +360,7 @@ 383 | 384 | if (iphp->rrp.peers->peer[i].sockaddr == pc->sockaddr && iphp->rrp.peers->peer[i].socklen == pc->socklen) { 385 | if (conf->hash || conf->hmac || conf->text) { 386 | - ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &conf->peers[i].digest, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires); 387 | + ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &conf->peers[i].digest, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires, conf->cookie_secure, conf->cookie_httponly); 388 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &conf->peers[i].digest, i); 389 | } else { 390 | ngx_str_t route; 391 | @@ -373,7 +375,7 @@ 392 | } 393 | ngx_snprintf(route.data, route.len, "%d", i); 394 | route.len = ngx_strlen(route.data); 395 | - ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &route, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires); 396 | + ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &route, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires, conf->cookie_secure, conf->cookie_httponly); 397 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &tmp, i); 398 | } 399 | break; /* found and hopefully the cookie have been set */ 400 | @@ -401,6 +403,8 @@ 401 | ngx_str_t path = ngx_string(""); 402 | ngx_str_t hmac_key = ngx_string(""); 403 | time_t expires = NGX_CONF_UNSET; 404 | + unsigned secure = 0; 405 | + unsigned httponly = 0; 406 | ngx_http_sticky_misc_hash_pt hash = NGX_CONF_UNSET_PTR; 407 | ngx_http_sticky_misc_hmac_pt hmac = NULL; 408 | ngx_http_sticky_misc_text_pt text = NULL; 409 | @@ -477,6 +481,16 @@ 410 | continue; 411 | } 412 | 413 | + if (ngx_strncmp(value[i].data, "secure", 6) == 0 && value[i].len == 6) { 414 | + secure = 1; 415 | + continue; 416 | + } 417 | + 418 | + if (ngx_strncmp(value[i].data, "httponly", 8) == 0 && value[i].len == 8) { 419 | + httponly = 1; 420 | + continue; 421 | + } 422 | + 423 | /* is "text=" is starting the argument ? */ 424 | if ((u_char *)ngx_strstr(value[i].data, "text=") == value[i].data) { 425 | 426 | @@ -646,6 +660,8 @@ 427 | sticky_conf->cookie_domain = domain; 428 | sticky_conf->cookie_path = path; 429 | sticky_conf->cookie_expires = expires; 430 | + sticky_conf->cookie_secure = secure; 431 | + sticky_conf->cookie_httponly = httponly; 432 | sticky_conf->hash = hash; 433 | sticky_conf->hmac = hmac; 434 | sticky_conf->text = text; 435 | Index: ngx_http_sticky_misc.c 436 | =================================================================== 437 | --- ngx_http_sticky_misc.c (revision 49) 438 | +++ ngx_http_sticky_misc.c (working copy) 439 | @@ -16,7 +16,7 @@ 440 | #define ngx_str_set(str, text) (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text 441 | #endif 442 | 443 | -ngx_int_t ngx_http_sticky_misc_set_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires) 444 | +ngx_int_t ngx_http_sticky_misc_set_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires, unsigned secure, unsigned httponly) 445 | { 446 | u_char *cookie, *p; 447 | size_t len; 448 | @@ -48,6 +48,16 @@ 449 | len += sizeof("; Path=") - 1 + path->len; 450 | } 451 | 452 | + /* ; Secure */ 453 | + if (secure) { 454 | + len += sizeof("; Secure") - 1; 455 | + } 456 | + 457 | + /* ; HttpOnly */ 458 | + if (httponly) { 459 | + len += sizeof("; HttpOnly") - 1; 460 | + } 461 | + 462 | cookie = ngx_pnalloc(r->pool, len); 463 | if (cookie == NULL) { 464 | return NGX_ERROR; 465 | @@ -72,6 +82,14 @@ 466 | p = ngx_copy(p, path->data, path->len); 467 | } 468 | 469 | + if (secure) { 470 | + p = ngx_copy(p, "; Secure", sizeof("; Secure") - 1); 471 | + } 472 | + 473 | + if (httponly) { 474 | + p = ngx_copy(p, "; HttpOnly", sizeof("; HttpOnly") - 1); 475 | + } 476 | + 477 | part = &r->headers_out.headers.part; 478 | elt = part->elts; 479 | set_cookie = NULL; 480 | 481 | 482 | -------------------------------------------------------------------------------- /patches/upstream_check.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ngx_http_sticky_module.c b/ngx_http_sticky_module.c 2 | index 9967428..f13d4b5 100644 3 | --- a/ngx_http_sticky_module.c 4 | +++ b/ngx_http_sticky_module.c 5 | @@ -299,6 +299,16 @@ static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data) 6 | return NGX_BUSY; 7 | } 8 | 9 | +#if (NGX_UPSTREAM_CHECK_MODULE) 10 | + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, 11 | + "get sticky peer, check_index: %ui", 12 | + peer->check_index); 13 | + 14 | + if (ngx_http_check_peer_down(peer->check_index)) { 15 | + return NGX_BUSY; 16 | + } 17 | +#endif 18 | + 19 | /* if it's been ignored for long enought (fail_timeout), reset timeout */ 20 | /* do this check before testing peer->fails ! :) */ 21 | if (now - peer->accessed > peer->fail_timeout) { 22 | @@ -315,6 +325,14 @@ static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data) 23 | /* ensure the peer is not marked as down */ 24 | if (!peer->down) { 25 | 26 | +#if (NGX_UPSTREAM_CHECK_MODULE) 27 | + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, 28 | + "get sticky peer, check_index: %ui", 29 | + peer->check_index); 30 | + 31 | + if (!ngx_http_check_peer_down(peer->check_index)) { 32 | +#endif 33 | + 34 | /* if it's not failedi, use it */ 35 | if (peer->max_fails == 0 || peer->fails < peer->max_fails) { 36 | selected_peer = (ngx_int_t)n; 37 | @@ -329,6 +347,9 @@ static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data) 38 | /* mark the peer as tried */ 39 | iphp->rrp.tried[n] |= m; 40 | } 41 | +#if (NGX_UPSTREAM_CHECK_MODULE) 42 | + } 43 | +#endif 44 | } 45 | } 46 | } --------------------------------------------------------------------------------