src/http/modules/ngx_http_upstream_sticky_module.c - nginx

Global variables defined

Data types defined

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Nginx, Inc.
  3. */


  4. #include <ngx_config.h>
  5. #include <ngx_core.h>
  6. #include <ngx_http.h>
  7. #include <ngx_md5.h>


  8. #define NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES  2145916555

  9. #define ngx_http_upstream_sticky_sess_node(rbn, mb)                           \
  10.     (ngx_http_upstream_sticky_sess_node_t *)                                  \
  11.         ((char *) (rbn) - offsetof(ngx_http_upstream_sticky_sess_node_t, mb))


  12. typedef union {
  13.     u_char                                      md5[16];
  14.     ngx_uint_t                                  hash;
  15. } ngx_http_upstream_sticky_sess_key_t;


  16. typedef struct {
  17.     ngx_rbtree_t                                rbtree;
  18.     ngx_rbtree_node_t                           sentinel;

  19.     ngx_rbtree_t                                exp_rbtree;
  20.     ngx_rbtree_node_t                           exp_sentinel;
  21. } ngx_http_upstream_sticky_sess_shared_t;


  22. typedef struct {
  23.     ngx_http_upstream_sticky_sess_shared_t     *sh;
  24.     ngx_slab_pool_t                            *shpool;
  25.     ngx_str_t                                  *host;

  26.     ngx_msec_t                                  timeout;
  27.     ngx_event_t                                 event;
  28. } ngx_http_upstream_sticky_sess_t;


  29. /* session data: mapping of session ID hash to server ID */
  30. typedef struct {
  31.     ngx_rbtree_node_t                           rbnode;
  32.     ngx_rbtree_node_t                           enode;

  33.     union {
  34.         u_char                                  md5[16];
  35.         ngx_uint_t                              hash;
  36.     } u;

  37.     ngx_msec_t                                  last;

  38.     u_char                                      sid_len;
  39.     u_char                                      sid[NGX_HTTP_UPSTREAM_SID_LEN];
  40. } ngx_http_upstream_sticky_sess_node_t;


  41. /* per-upstream sticky configuration */
  42. typedef struct {
  43.     ngx_http_upstream_init_pt                   original_init_upstream;
  44.     ngx_http_upstream_init_peer_pt              original_init_peer;

  45.     ngx_array_t                                *lookup_vars; /* of ngx_int_t */
  46.     ngx_array_t                                *create_vars; /* of ngx_int_t */
  47.     ngx_shm_zone_t                             *shm_zone;    /* sessions */

  48.     ngx_str_t                                   cookie_name;
  49.     ngx_http_complex_value_t                   *cookie_domain;
  50.     ngx_str_t                                   cookie_path;
  51.     time_t                                      cookie_expires;
  52.     ngx_http_complex_value_t                   *cookie_samesite;
  53.     unsigned                                    cookie_httponly:1;
  54.     unsigned                                    cookie_secure:1;
  55.     unsigned                                    learn_after_headers:1;
  56. } ngx_http_upstream_sticky_srv_conf_t;


  57. typedef struct {
  58.     void                                       *original_data;
  59.     ngx_http_request_t                         *request;

  60.     ngx_http_upstream_sticky_srv_conf_t        *conf;

  61.     ngx_str_t                                   id;
  62.     ngx_table_elt_t                            *cookie;

  63.     ngx_event_get_peer_pt                       original_get_peer;
  64.     ngx_event_free_peer_pt                      original_free_peer;

  65. #if (NGX_HTTP_SSL)
  66.     ngx_event_set_peer_session_pt               original_set_session;
  67.     ngx_event_save_peer_session_pt              original_save_session;
  68. #endif

  69.     ngx_event_notify_peer_pt                    original_notify;

  70. } ngx_http_upstream_sticky_peer_data_t;


  71. static ngx_int_t ngx_http_upstream_sticky_init_upstream(ngx_conf_t *cf,
  72.     ngx_http_upstream_srv_conf_t *us);
  73. static ngx_int_t ngx_http_upstream_sticky_init_peer(ngx_http_request_t *r,
  74.     ngx_http_upstream_srv_conf_t *us);
  75. static ngx_int_t ngx_http_upstream_sticky_get_id(
  76.     ngx_http_upstream_sticky_srv_conf_t *stcf, ngx_http_request_t *r,
  77.     ngx_array_t *vars, ngx_str_t *id);
  78. static void ngx_http_upstream_sticky_sess_init_key(ngx_str_t *sess_id,
  79.     ngx_http_upstream_sticky_sess_key_t *key);
  80. static ngx_int_t ngx_http_upstream_sticky_get_peer(ngx_peer_connection_t *pc,
  81.     void *data);
  82. static void ngx_http_upstream_sticky_free_peer(ngx_peer_connection_t *pc,
  83.     void *data, ngx_uint_t state);
  84. static void ngx_http_upstream_sticky_learn_peer(
  85.     ngx_http_upstream_sticky_peer_data_t *stp, ngx_peer_connection_t *pc);


  86. #if (NGX_HTTP_SSL)
  87. static ngx_int_t ngx_http_upstream_sticky_set_session(
  88.     ngx_peer_connection_t *pc, void *data);
  89. static void ngx_http_upstream_sticky_save_session(ngx_peer_connection_t *pc,
  90.     void *data);
  91. #endif


  92. static void ngx_http_upstream_sticky_notify_peer(
  93.     ngx_peer_connection_t *pc, void *data, ngx_uint_t type);
  94. static ngx_int_t ngx_http_upstream_sticky_cookie_insert(
  95.     ngx_peer_connection_t *pc, ngx_http_upstream_sticky_peer_data_t *stp);
  96. static ngx_int_t ngx_http_upstream_sticky_samesite(ngx_str_t *value);


  97. static ngx_http_upstream_sticky_sess_node_t *
  98.     ngx_http_upstream_sticky_sess_lookup(ngx_http_upstream_sticky_sess_t *sess,
  99.     ngx_http_upstream_sticky_sess_key_t *key);
  100. static ngx_http_upstream_sticky_sess_node_t *
  101.     ngx_http_upstream_sticky_sess_create(ngx_http_upstream_sticky_sess_t *sess,
  102.     ngx_http_upstream_sticky_sess_key_t *key, ngx_str_t *sid);
  103. static void ngx_http_upstream_sticky_sess_rbtree_insert_value(
  104.     ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
  105.     ngx_rbtree_node_t *sentinel);
  106. static void ngx_http_upstream_sticky_sess_timer_handler(ngx_event_t *ev);
  107. static ngx_msec_t ngx_http_upstream_sticky_sess_expire(
  108.     ngx_http_upstream_sticky_sess_t *sess, ngx_uint_t force);
  109. static ngx_int_t ngx_http_upstream_sticky_sess_init_zone(
  110.     ngx_shm_zone_t *shm_zone, void *data);

  111. static void *ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf);
  112. static char *ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd,
  113.     void *conf);
  114. static char *ngx_http_upstream_sticky_cookie(ngx_conf_t *cf,
  115.     ngx_http_upstream_sticky_srv_conf_t *stcf);
  116. static char *ngx_http_upstream_sticky_learn(ngx_conf_t *cf,
  117.     ngx_http_upstream_sticky_srv_conf_t *stcf,
  118.     ngx_http_upstream_srv_conf_t *us);

  119. static ngx_int_t ngx_http_upstream_sticky_init_worker(ngx_cycle_t *cycle);


  120. static u_char expires[] =
  121.     "; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=315360000";
  122. static u_char httponly[] = "; httponly";
  123. static u_char secure[] = "; secure";


  124. static ngx_command_t  ngx_http_upstream_sticky_commands[] = {

  125.     { ngx_string("sticky"),
  126.       NGX_HTTP_UPS_CONF|NGX_CONF_2MORE,
  127.       ngx_http_upstream_sticky,
  128.       0,
  129.       0,
  130.       NULL },

  131.       ngx_null_command
  132. };


  133. static ngx_http_module_t
  134. ngx_http_upstream_sticky_module_ctx = {
  135.     NULL,                                 /* preconfiguration */
  136.     NULL,                                 /* postconfiguration */

  137.     NULL,                                 /* create main configuration */
  138.     NULL,                                 /* init main configuration */

  139.     ngx_http_upstream_sticky_create_conf, /* create server configuration */
  140.     NULL,                                 /* merge server configuration */

  141.     NULL,                                 /* create location configuration */
  142.     NULL                                  /* merge location configuration */
  143. };


  144. ngx_module_t
  145. ngx_http_upstream_sticky_module =
  146. {
  147.     NGX_MODULE_V1,

  148.     &ngx_http_upstream_sticky_module_ctx, /* module context */
  149.     ngx_http_upstream_sticky_commands,    /* module directives */

  150.     NGX_HTTP_MODULE,                      /* module type */
  151.     NULL,                                 /* init master */

  152.     NULL,                                 /* init module */
  153.     ngx_http_upstream_sticky_init_worker, /* init process */

  154.     NULL,                                 /* init thread */
  155.     NULL,                                 /* exit thread */

  156.     NULL,                                 /* exit process */
  157.     NULL,                                 /* exit master */

  158.     NGX_MODULE_V1_PADDING
  159. };


  160. static ngx_int_t
  161. ngx_http_upstream_sticky_init_upstream(ngx_conf_t *cf,
  162.     ngx_http_upstream_srv_conf_t *us)
  163. {
  164.     ngx_http_upstream_sticky_srv_conf_t  *stcf;

  165.     stcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_sticky_module);

  166.     if (stcf->original_init_upstream(cf, us) != NGX_OK) {
  167.         return NGX_ERROR;
  168.     }

  169.     stcf->original_init_peer = us->peer.init;
  170.     us->peer.init = ngx_http_upstream_sticky_init_peer;

  171.     return NGX_OK;
  172. }


  173. static ngx_int_t
  174. ngx_http_upstream_sticky_init_peer(ngx_http_request_t *r,
  175.     ngx_http_upstream_srv_conf_t *us)
  176. {
  177.     ngx_int_t                              rc;
  178.     ngx_http_upstream_t                   *u;
  179.     ngx_http_upstream_sticky_srv_conf_t   *stcf;
  180.     ngx_http_upstream_sticky_peer_data_t  *stp;

  181.     stcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_sticky_module);

  182.     stp = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_sticky_peer_data_t));
  183.     if (stp == NULL) {
  184.         return NGX_ERROR;
  185.     }

  186.     rc = stcf->original_init_peer(r, us);

  187.     if (rc != NGX_OK) {
  188.         return rc;
  189.     }

  190.     u = r->upstream;

  191.     stp->original_data = u->peer.data;
  192.     stp->original_get_peer = u->peer.get;
  193.     stp->original_free_peer = u->peer.free;

  194.     stp->request = r;
  195.     stp->conf = stcf;

  196.     u->peer.get = ngx_http_upstream_sticky_get_peer;
  197.     u->peer.free = ngx_http_upstream_sticky_free_peer;
  198.     u->peer.data = stp;

  199. #if (NGX_HTTP_SSL)
  200.     stp->original_set_session = u->peer.set_session;
  201.     stp->original_save_session = u->peer.save_session;
  202.     u->peer.set_session = ngx_http_upstream_sticky_set_session;
  203.     u->peer.save_session = ngx_http_upstream_sticky_save_session;
  204. #endif

  205.     if (u->peer.notify || stcf->learn_after_headers) {
  206.         stp->original_notify = u->peer.notify;
  207.         u->peer.notify = ngx_http_upstream_sticky_notify_peer;
  208.     }

  209.     ngx_http_upstream_sticky_get_id(stcf, r, stcf->lookup_vars, &stp->id);

  210.     return NGX_OK;
  211. }


  212. static ngx_int_t
  213. ngx_http_upstream_sticky_get_id(ngx_http_upstream_sticky_srv_conf_t *stcf,
  214.     ngx_http_request_t *r, ngx_array_t *vars, ngx_str_t *id)
  215. {
  216.     ngx_int_t                  *index;
  217.     ngx_uint_t                  i;
  218.     ngx_http_variable_value_t  *v;

  219.     index = vars->elts;

  220.     for (i = 0; i < vars->nelts; i++) {

  221.         v = ngx_http_get_flushed_variable(r, index[i]);

  222.         if (v == NULL || v->not_found || v->len == 0) {
  223.             continue;
  224.         }

  225.         id->data = v->data;
  226.         id->len = v->len;

  227.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  228.                        "sticky: using \"%v\" found in variable #%i", v, i + 1);

  229.         return NGX_OK;
  230.     }

  231.     ngx_str_null(id);

  232.     return NGX_DONE;
  233. }


  234. static ngx_inline void
  235. ngx_http_upstream_sticky_sess_init_key(ngx_str_t *sess_id,
  236.     ngx_http_upstream_sticky_sess_key_t *key)
  237. {
  238.     ngx_md5_t  md5;

  239.     ngx_md5_init(&md5);
  240.     ngx_md5_update(&md5, sess_id->data, sess_id->len);
  241.     ngx_md5_final(key->md5, &md5);
  242. }


  243. static ngx_int_t
  244. ngx_http_upstream_sticky_get_peer(ngx_peer_connection_t *pc, void *data)
  245. {
  246.     ngx_http_upstream_sticky_peer_data_t  *stp = data;

  247.     ngx_int_t                              rc;
  248.     ngx_str_t                              sid;
  249.     ngx_http_upstream_sticky_sess_t       *sess;
  250.     ngx_http_upstream_sticky_sess_key_t    key;
  251.     ngx_http_upstream_sticky_sess_node_t  *sn;
  252.     u_char                                 sid_data[NGX_HTTP_UPSTREAM_SID_LEN];

  253.     if (pc->hint == NULL && stp->conf->shm_zone && stp->id.len) {

  254.         /* request holds session ID, extract server ID from session */

  255.         sess = stp->conf->shm_zone->data;

  256.         ngx_http_upstream_sticky_sess_init_key(&stp->id, &key);

  257.         ngx_shmtx_lock(&sess->shpool->mutex);

  258.         sn = ngx_http_upstream_sticky_sess_lookup(sess, &key);
  259.         if (sn == NULL) {
  260.             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  261.                            "sticky: session \"%V\" not found", &stp->id);

  262.         } else {
  263.             ngx_memcpy(sid_data, sn->sid, sn->sid_len);
  264.             sid.len = sn->sid_len;
  265.             sid.data = sid_data;
  266.             pc->hint = &sid;

  267.             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  268.                            "sticky: session \"%V\", SID \"%V\"",
  269.                            &stp->id, &sid);
  270.         }

  271.         ngx_shmtx_unlock(&sess->shpool->mutex);

  272.     } else if (pc->hint == NULL && stp->id.len) {

  273.         /* request holds server ID */

  274.         pc->hint = &stp->id;
  275.     }

  276.     rc = stp->original_get_peer(pc, stp->original_data);

  277.     pc->hint = NULL;

  278.     if (rc != NGX_OK && rc != NGX_DONE) {
  279.         return rc;
  280.     }

  281.     if (stp->conf->cookie_name.len == 0) {
  282.         return rc;
  283.     }

  284.     if (ngx_http_upstream_sticky_cookie_insert(pc, stp) != NGX_OK) {
  285.         return NGX_ERROR;
  286.     }

  287.     return rc;
  288. }


  289. static void
  290. ngx_http_upstream_sticky_free_peer(ngx_peer_connection_t *pc, void *data,
  291.     ngx_uint_t state)
  292. {
  293.     ngx_http_upstream_sticky_peer_data_t  *stp = data;

  294.     if (state & (NGX_PEER_FAILED|NGX_PEER_NEXT)) {
  295.         goto done;
  296.     }

  297.     if (stp->conf->shm_zone && !stp->conf->learn_after_headers) {
  298.         ngx_http_upstream_sticky_learn_peer(stp, pc);
  299.     }

  300. done:

  301.     stp->original_free_peer(pc, stp->original_data, state);
  302. }


  303. static void
  304. ngx_http_upstream_sticky_learn_peer(ngx_http_upstream_sticky_peer_data_t *stp,
  305.     ngx_peer_connection_t *pc)
  306. {
  307.     ngx_str_t                              sess_id;
  308.     ngx_msec_t                             now;
  309.     ngx_time_t                            *tp;
  310.     ngx_uint_t                             create;
  311.     ngx_http_request_t                    *r;
  312.     ngx_http_upstream_sticky_sess_t       *sess;
  313.     ngx_http_upstream_sticky_sess_key_t    key;
  314.     ngx_http_upstream_sticky_srv_conf_t   *stcf;
  315.     ngx_http_upstream_sticky_sess_node_t  *sn;

  316.     if (pc->sid == NULL) {
  317.         ngx_log_error(NGX_LOG_WARN, pc->log, 0,
  318.                       "balancer does not support sticky");
  319.         return;
  320.     }

  321.     stcf = stp->conf;

  322.     r = stp->request;

  323.     sess = stcf->shm_zone->data;

  324.     if (ngx_http_upstream_sticky_get_id(stcf, r, stcf->create_vars, &sess_id)
  325.         == NGX_OK)
  326.     {
  327.         create = 1;

  328.     } else if (stp->id.len) {
  329.         sess_id = stp->id;
  330.         create = 0;

  331.     } else {
  332.         return;
  333.     }

  334.     tp = ngx_timeofday();
  335.     now = tp->sec * 1000 + tp->msec;

  336.     ngx_http_upstream_sticky_sess_init_key(&sess_id, &key);

  337.     ngx_shmtx_lock(&sess->shpool->mutex);

  338.     sn = ngx_http_upstream_sticky_sess_lookup(sess, &key);

  339.     if (sn) {
  340.         if (pc->sid->len != sn->sid_len
  341.             || ngx_memcmp(pc->sid->data, sn->sid, sn->sid_len) != 0)
  342.         {
  343.             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  344.                            "sticky: session \"%V\" reused for SID \"%V\"",
  345.                            &sess_id, pc->sid);

  346.             sn->sid_len = pc->sid->len;
  347.             ngx_memcpy(sn->sid, pc->sid->data, pc->sid->len);
  348.         }

  349.         ngx_rbtree_delete(&sess->sh->exp_rbtree, &sn->enode);
  350.         sn->last = now;
  351.         sn->enode.key = sn->last;
  352.         ngx_rbtree_insert(&sess->sh->exp_rbtree, &sn->enode);

  353.         ngx_shmtx_unlock(&sess->shpool->mutex);
  354.         return;
  355.     }

  356.     if (create) {
  357.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  358.                        "sticky: creating session \"%V\", SID \"%V\"",
  359.                        &sess_id, pc->sid);

  360.         sn = ngx_http_upstream_sticky_sess_create(sess, &key, pc->sid);

  361.         if (sn) {
  362.             sn->last = now;
  363.             sn->enode.key = sn->last;
  364.             ngx_rbtree_insert(&sess->sh->exp_rbtree, &sn->enode);

  365.             if (!sess->event.timer_set) {
  366.                 ngx_add_timer(&sess->event, sess->timeout);
  367.             }
  368.         }
  369.     }

  370.     ngx_shmtx_unlock(&sess->shpool->mutex);
  371. }


  372. #if (NGX_HTTP_SSL)

  373. static ngx_int_t
  374. ngx_http_upstream_sticky_set_session(ngx_peer_connection_t *pc, void *data)
  375. {
  376.     ngx_http_upstream_sticky_peer_data_t  *stp = data;

  377.     return stp->original_set_session(pc, stp->original_data);
  378. }


  379. static void
  380. ngx_http_upstream_sticky_save_session(ngx_peer_connection_t *pc, void *data)
  381. {
  382.     ngx_http_upstream_sticky_peer_data_t  *stp = data;

  383.     stp->original_save_session(pc, stp->original_data);
  384. }

  385. #endif


  386. static void
  387. ngx_http_upstream_sticky_notify_peer(ngx_peer_connection_t *pc, void *data,
  388.     ngx_uint_t type)
  389. {
  390.     ngx_http_upstream_sticky_peer_data_t  *stp = data;

  391.     if (type == NGX_HTTP_UPSTREAM_NOTIFY_HEADER
  392.         && stp->conf->learn_after_headers)
  393.     {
  394.         ngx_http_upstream_sticky_learn_peer(stp, pc);
  395.     }

  396.     if (stp->original_notify) {
  397.         stp->original_notify(pc, stp->original_data, type);
  398.     }
  399. }


  400. static ngx_int_t
  401. ngx_http_upstream_sticky_cookie_insert(ngx_peer_connection_t *pc,
  402.     ngx_http_upstream_sticky_peer_data_t *stp)
  403. {
  404.     size_t                                len;
  405.     u_char                               *data, *p;
  406.     ngx_str_t                             domain, samesite;
  407.     ngx_table_elt_t                      *cookie;
  408.     ngx_http_request_t                   *r;
  409.     ngx_http_upstream_sticky_srv_conf_t  *stcf;

  410.     stcf = stp->conf;
  411.     r = stp->request;

  412.     if (pc->sid == NULL) {
  413.         ngx_log_error(NGX_LOG_WARN, pc->log, 0,
  414.                       "balancer does not support sticky");
  415.         return NGX_OK;
  416.     }

  417. #if (NGX_DEBUG)

  418.     if (stp->id.len) {

  419.         /* check that the selected peer matches SID from request */

  420.         if (pc->sid->len != stp->id.len
  421.             || ngx_memcmp(pc->sid->data, stp->id.data, stp->id.len) != 0)
  422.         {
  423.             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  424.                            "sticky: server with requested SID is unavailable");
  425.         }
  426.     }

  427. #endif

  428.     len = stcf->cookie_name.len + 1 + pc->sid->len + stcf->cookie_path.len;

  429.     ngx_str_set(&domain, "");

  430.     if (stcf->cookie_domain) {
  431.         if (ngx_http_complex_value(r, stcf->cookie_domain, &domain)
  432.             != NGX_OK)
  433.         {
  434.             return NGX_ERROR;
  435.         }
  436.     }

  437.     if (domain.len) {
  438.         len += sizeof("; domain=") - 1 + domain.len;
  439.     }

  440.     if (stcf->cookie_expires != (time_t) NGX_CONF_UNSET) {
  441.         len += sizeof(expires) - 1 + NGX_TIME_T_LEN;
  442.     }

  443.     if (stcf->cookie_httponly) {
  444.         len += sizeof(httponly) - 1;
  445.     }

  446.     if (stcf->cookie_secure) {
  447.         len += sizeof(secure) - 1;
  448.     }

  449.     ngx_str_set(&samesite, "");

  450.     if (stcf->cookie_samesite) {

  451.         if (ngx_http_complex_value(r, stcf->cookie_samesite, &samesite)
  452.             != NGX_OK)
  453.         {
  454.             return NGX_ERROR;
  455.         }

  456.         if (stcf->cookie_samesite->lengths && samesite.len
  457.             && ngx_http_upstream_sticky_samesite(&samesite) != NGX_OK)
  458.         {
  459.             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  460.                            "sticky: invalid cookie samesite value \"%V\"",
  461.                            &samesite);
  462.             ngx_str_set(&samesite, "strict");
  463.         }
  464.     }

  465.     if (samesite.len) {
  466.         len += sizeof("; samesite=") - 1 + samesite.len;
  467.     }

  468.     data = ngx_pnalloc(r->pool, len);
  469.     if (data == NULL) {
  470.         return NGX_ERROR;
  471.     }

  472.     p = ngx_copy(data, stcf->cookie_name.data, stcf->cookie_name.len);
  473.     *p++ = '=';
  474.     p = ngx_copy(p, pc->sid->data, pc->sid->len);

  475.     if (stcf->cookie_expires != (time_t) NGX_CONF_UNSET) {

  476.         if (stcf->cookie_expires == NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES) {
  477.             p = ngx_cpymem(p, expires, sizeof(expires) - 1);

  478.         } else {
  479.             p = ngx_cpymem(p, "; expires=", 10);
  480.             p = ngx_http_cookie_time(p, ngx_time() + stcf->cookie_expires);
  481.             p = ngx_sprintf(p, "; max-age=%T", stcf->cookie_expires);
  482.         }
  483.     }

  484.     if (domain.len) {
  485.         p = ngx_cpymem(p, "; domain=", 9);
  486.         p = ngx_copy(p, domain.data, domain.len);
  487.     }

  488.     if (stcf->cookie_httponly) {
  489.         p = ngx_copy(p, httponly, sizeof(httponly) - 1);
  490.     }

  491.     if (stcf->cookie_secure) {
  492.         p = ngx_copy(p, secure, sizeof(secure) - 1);
  493.     }

  494.     if (samesite.len) {
  495.         p = ngx_cpymem(p, "; samesite=", 11);
  496.         p = ngx_copy(p, samesite.data, samesite.len);
  497.     }

  498.     p = ngx_cpymem(p, stcf->cookie_path.data, stcf->cookie_path.len);

  499.     cookie = stp->cookie;

  500.     if (cookie == NULL) {

  501.         cookie = ngx_list_push(&r->headers_out.headers);
  502.         if (cookie == NULL) {
  503.             return NGX_ERROR;
  504.         }

  505.         cookie->hash = 1;
  506.         cookie->next = NULL;
  507.         ngx_str_set(&cookie->key, "Set-Cookie");

  508.         stp->cookie = cookie;
  509.     }

  510.     cookie->value.len = p - data;
  511.     cookie->value.data = data;

  512.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  513.                    "sticky: set cookie: \"%V\"", &cookie->value);

  514.     return NGX_OK;
  515. }


  516. static ngx_int_t
  517. ngx_http_upstream_sticky_samesite(ngx_str_t *value)
  518. {
  519.     ngx_uint_t  i;

  520.     static ngx_str_t samesite[] = {
  521.         ngx_string("strict"),
  522.         ngx_string("lax"),
  523.         ngx_string("none"),
  524.         ngx_null_string
  525.     };

  526.     for (i = 0; samesite[i].len != 0; i++) {

  527.         if (samesite[i].len == value->len
  528.             && ngx_strncasecmp(samesite[i].data, value->data, value->len) == 0)
  529.         {
  530.             return NGX_OK;
  531.         }
  532.     }

  533.     return NGX_ERROR;
  534. }


  535. static ngx_http_upstream_sticky_sess_node_t *
  536. ngx_http_upstream_sticky_sess_lookup(ngx_http_upstream_sticky_sess_t *sess,
  537.     ngx_http_upstream_sticky_sess_key_t *key)
  538. {
  539.     ngx_int_t                              rc;
  540.     ngx_uint_t                             hash;
  541.     ngx_rbtree_node_t                     *node, *sentinel;
  542.     ngx_http_upstream_sticky_sess_node_t  *sn;

  543.     hash = key->hash;
  544.     node = sess->sh->rbtree.root;
  545.     sentinel = sess->sh->rbtree.sentinel;

  546.     while (node != sentinel) {

  547.         if (hash < node->key) {
  548.             node = node->left;
  549.             continue;
  550.         }

  551.         if (hash > node->key) {
  552.             node = node->right;
  553.             continue;
  554.         }

  555.         /* hash == node->key */

  556.         do {

  557.             sn = (ngx_http_upstream_sticky_sess_node_t *) node;

  558.             rc = ngx_memcmp(key->md5, sn->u.md5, 16);

  559.             if (rc == 0) {
  560.                 return sn;
  561.             }

  562.             node = (rc < 0) ? node->left : node->right;

  563.         } while (node != sentinel && hash == node->key);

  564.         break;
  565.     }

  566.     return NULL;
  567. }


  568. static ngx_http_upstream_sticky_sess_node_t *
  569. ngx_http_upstream_sticky_sess_create(ngx_http_upstream_sticky_sess_t *sess,
  570.     ngx_http_upstream_sticky_sess_key_t *key, ngx_str_t *sid)
  571. {
  572.     size_t                                 n;
  573.     ngx_rbtree_node_t                     *node;
  574.     ngx_http_upstream_sticky_sess_node_t  *sn;

  575.     n = sizeof(ngx_http_upstream_sticky_sess_node_t);

  576.     sn = ngx_slab_alloc_locked(sess->shpool, n);
  577.     if (sn == NULL) {

  578.         ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, 0,
  579.                       "could not allocate node%s, expiring least "
  580.                       "recently used session", sess->shpool->log_ctx);

  581.         (void) ngx_http_upstream_sticky_sess_expire(sess, 1);

  582.         sn = ngx_slab_alloc_locked(sess->shpool, n);
  583.         if (sn == NULL) {
  584.             ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
  585.                           "could not allocate node%s", sess->shpool->log_ctx);
  586.             return NULL;
  587.         }
  588.     }

  589.     ngx_memcpy(sn->u.md5, key->md5, 16);

  590.     sn->sid_len = sid->len;
  591.     ngx_memcpy(sn->sid, sid->data, sid->len);

  592.     node = &sn->rbnode;
  593.     node->key = sn->u.hash;

  594.     ngx_rbtree_insert(&sess->sh->rbtree, node);

  595.     return sn;
  596. }


  597. static void
  598. ngx_http_upstream_sticky_sess_rbtree_insert_value(ngx_rbtree_node_t *temp,
  599.     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
  600. {
  601.     ngx_rbtree_node_t                     **p;
  602.     ngx_http_upstream_sticky_sess_node_t  *sn, *snt;

  603.     for ( ;; ) {

  604.         if (node->key < temp->key) {

  605.             p = &temp->left;

  606.         } else if (node->key > temp->key) {

  607.             p = &temp->right;

  608.         } else { /* node->key == temp->key */

  609.             sn = (ngx_http_upstream_sticky_sess_node_t *) node;
  610.             snt = (ngx_http_upstream_sticky_sess_node_t *) temp;

  611.             p = (ngx_memcmp(sn->u.md5, snt->u.md5, 16) < 0)
  612.                 ? &temp->left : &temp->right;
  613.         }

  614.         if (*p == sentinel) {
  615.             break;
  616.         }

  617.         temp = *p;
  618.     }

  619.     *p = node;
  620.     node->parent = temp;
  621.     node->left = sentinel;
  622.     node->right = sentinel;
  623.     ngx_rbt_red(node);
  624. }


  625. static void
  626. ngx_http_upstream_sticky_sess_timer_handler(ngx_event_t *ev)
  627. {
  628.     ngx_msec_t                        wait;
  629.     ngx_http_upstream_sticky_sess_t  *sess;

  630.     sess = ev->data;

  631.     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
  632.                    "sticky: session timer");

  633.     ngx_shmtx_lock(&sess->shpool->mutex);

  634.     wait = ngx_http_upstream_sticky_sess_expire(sess, 0);

  635.     ngx_shmtx_unlock(&sess->shpool->mutex);

  636.     if (wait > 0) {
  637.         ngx_add_timer(&sess->event, wait);
  638.     }
  639. }


  640. static ngx_msec_t
  641. ngx_http_upstream_sticky_sess_expire(ngx_http_upstream_sticky_sess_t *sess,
  642.     ngx_uint_t force)
  643. {
  644.     ngx_msec_t                             now, wait;
  645.     ngx_time_t                            *tp;
  646.     ngx_rbtree_node_t                     *node, *next;
  647.     ngx_http_upstream_sticky_sess_node_t  *sn;

  648.     wait = 0;

  649.     tp = ngx_timeofday();
  650.     now = tp->sec * 1000 + tp->msec;

  651.     if (sess->sh->exp_rbtree.root == sess->sh->exp_rbtree.sentinel) {
  652.         return 0;
  653.     }

  654. #if (NGX_SUPPRESS_WARN)
  655.     next = NULL;
  656. #endif

  657.     for (node = ngx_rbtree_min(sess->sh->exp_rbtree.root,
  658.                                sess->sh->exp_rbtree.sentinel);
  659.          node;
  660.          node = next)
  661.     {

  662.         sn = ngx_http_upstream_sticky_sess_node(node, enode);
  663.         wait = sn->last + sess->timeout - now;

  664.         if (!force && (ngx_msec_int_t) wait > 0) {
  665.             break;
  666.         }

  667.         force = 0;

  668.         next = ngx_rbtree_next(&sess->sh->exp_rbtree, node);

  669.         /* remove node */
  670.         node = &sn->enode;
  671.         ngx_rbtree_delete(&sess->sh->exp_rbtree, node);

  672.         node = &sn->rbnode;
  673.         ngx_rbtree_delete(&sess->sh->rbtree, node);
  674.         ngx_slab_free_locked(sess->shpool, node);
  675.     }

  676.     return wait;
  677. }


  678. static ngx_int_t
  679. ngx_http_upstream_sticky_sess_init_zone(ngx_shm_zone_t *shm_zone, void *data)
  680. {
  681.     ngx_http_upstream_sticky_sess_t  *old_sess = data;

  682.     size_t                            len;
  683.     ngx_http_upstream_sticky_sess_t  *sess;

  684.     sess = shm_zone->data;

  685.     if (old_sess) {

  686.         if (ngx_strcmp(sess->host->data, old_sess->host->data) != 0) {

  687.             ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
  688.                           "sticky zone \"%V\" is used in upstream \"%V\" "
  689.                           "while previously it was used in upstream \"%V\"",
  690.                           &shm_zone->shm.name, sess->host, old_sess->host);

  691.             return NGX_ERROR;
  692.         }

  693.         sess->sh = old_sess->sh;
  694.         sess->shpool = old_sess->shpool;
  695.         return NGX_OK;
  696.     }

  697.     sess->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

  698.     if (shm_zone->shm.exists) {
  699.         sess->sh = sess->shpool->data;
  700.         return NGX_OK;
  701.     }

  702.     sess->sh = ngx_slab_alloc(sess->shpool,
  703.                               sizeof(ngx_http_upstream_sticky_sess_shared_t));
  704.     if (sess->sh == NULL) {
  705.         return NGX_ERROR;
  706.     }

  707.     sess->shpool->data = sess->sh;

  708.     ngx_rbtree_init(&sess->sh->rbtree, &sess->sh->sentinel,
  709.                     ngx_http_upstream_sticky_sess_rbtree_insert_value);

  710.     ngx_rbtree_init(&sess->sh->exp_rbtree, &sess->sh->exp_sentinel,
  711.                     ngx_rbtree_insert_timer_value);

  712.     len = sizeof(" in sticky session zone \"\"") + shm_zone->shm.name.len;

  713.     sess->shpool->log_ctx = ngx_slab_alloc(sess->shpool, len);
  714.     if (sess->shpool->log_ctx == NULL) {
  715.         return NGX_ERROR;
  716.     }

  717.     ngx_sprintf(sess->shpool->log_ctx, " in sticky session zone \"%V\"%Z",
  718.                 &shm_zone->shm.name);

  719.     sess->shpool->log_nomem = 0;

  720.     return NGX_OK;
  721. }


  722. static void *
  723. ngx_http_upstream_sticky_create_conf(ngx_conf_t *cf)
  724. {
  725.     ngx_http_upstream_sticky_srv_conf_t  *stcf;

  726.     stcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_sticky_srv_conf_t));
  727.     if (stcf == NULL) {
  728.         return NULL;
  729.     }

  730.     /*
  731.      * set by ngx_pcalloc():
  732.      *
  733.      *     stcf->original_init_upstream = NULL;
  734.      *     stcf->original_init_peer = NULL;
  735.      *
  736.      *     stcf->lookup_vars = NULL;
  737.      *     stcf->create_vars = NULL;
  738.      *     stcf->shm_zone = NULL;
  739.      *
  740.      *     stcf->cookie_name = { 0, NULL };
  741.      *     stcf->cookie_domain = NULL;
  742.      *     stcf->cookie_path = { 0, NULL };
  743.      *     stcf->cookie_httponly = 0;
  744.      *     stcf->cookie_secure = 0;
  745.      *     stcf->cookie_samesite = NULL;
  746.      *
  747.      *     stcf->learn_after_headers = 0;
  748.      */

  749.     stcf->cookie_expires = NGX_CONF_UNSET;

  750.     return stcf;
  751. }


  752. static char *
  753. ngx_http_upstream_sticky(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  754. {
  755.     ngx_str_t                            *value;
  756.     ngx_int_t                            *indexp, index;
  757.     ngx_uint_t                            i;
  758.     ngx_http_upstream_srv_conf_t         *us;
  759.     ngx_http_upstream_sticky_srv_conf_t  *stcf;

  760.     us = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
  761.     stcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_sticky_module);

  762.     if (stcf->lookup_vars != NULL) {
  763.         return "is duplicate";
  764.     }

  765.     stcf->lookup_vars = ngx_array_create(cf->pool, 1, sizeof(ngx_int_t));
  766.     if (stcf->lookup_vars == NULL) {
  767.         return NGX_CONF_ERROR;
  768.     }

  769.     stcf->original_init_upstream = us->peer.init_upstream
  770.                                    ? us->peer.init_upstream
  771.                                    : ngx_http_upstream_init_round_robin;

  772.     us->peer.init_upstream = ngx_http_upstream_sticky_init_upstream;

  773.     value = cf->args->elts;

  774.     if (ngx_strcmp(value[1].data, "cookie") == 0) {
  775.         return ngx_http_upstream_sticky_cookie(cf, stcf);

  776.     } else if (ngx_strcmp(value[1].data, "route") == 0) {

  777.         for (i = 2; i < cf->args->nelts; i++) {

  778.             if (value[i].data[0] != '$') {
  779.                 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  780.                                    "invalid variable name \"%V\"", &value[i]);
  781.                 return NGX_CONF_ERROR;
  782.             }

  783.             value[i].len--;
  784.             value[i].data++;

  785.             index = ngx_http_get_variable_index(cf, &value[i]);
  786.             if (index == NGX_ERROR) {
  787.                 return NGX_CONF_ERROR;
  788.             }

  789.             indexp = ngx_array_push(stcf->lookup_vars);
  790.             if (indexp == NULL) {
  791.                 return NGX_CONF_ERROR;
  792.             }

  793.             *indexp = index;
  794.         }

  795.         return NGX_CONF_OK;

  796.     } else if (ngx_strcmp(value[1].data, "learn") == 0) {
  797.         return ngx_http_upstream_sticky_learn(cf, stcf, us);
  798.     }

  799.     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown parameter \"%V\"",
  800.                        &value[1]);
  801.     return NGX_CONF_ERROR;
  802. }


  803. static char *
  804. ngx_http_upstream_sticky_cookie(ngx_conf_t *cf,
  805.     ngx_http_upstream_sticky_srv_conf_t *stcf)
  806. {
  807.     u_char                            *p;
  808.     ngx_str_t                          name, *value;
  809.     ngx_int_t                          index, *indexp;
  810.     ngx_uint_t                         i;
  811.     ngx_http_compile_complex_value_t   ccv;

  812.     value = cf->args->elts;

  813.     if (value[2].len == 0) {
  814.         return "empty cookie name";
  815.     }

  816.     stcf->cookie_name = value[2];

  817.     for (i = 3; i < cf->args->nelts; i++) {

  818.         if (ngx_strncmp(value[i].data, "domain=", 7) == 0) {

  819.             if (stcf->cookie_domain != NULL) {
  820.                 return "parameter \"domain\" is duplicate";
  821.             }

  822.             value[i].data += 7;
  823.             value[i].len -= 7;

  824.             if (value[i].len == 0) {
  825.                 return "no value for \"domain\"";
  826.             }

  827.             ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  828.             stcf->cookie_domain = ngx_palloc(cf->pool,
  829.                                              sizeof(ngx_http_complex_value_t));
  830.             if (stcf->cookie_domain == NULL) {
  831.                 return NGX_CONF_ERROR;
  832.             }

  833.             ccv.cf = cf;
  834.             ccv.value = &value[i];
  835.             ccv.complex_value = stcf->cookie_domain;

  836.             if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  837.                 return NGX_CONF_ERROR;
  838.             }

  839.         } else if (ngx_strncmp(value[i].data, "path=", 5) == 0) {

  840.             if (stcf->cookie_path.data != NULL) {
  841.                 return "parameter \"path\" is duplicate";
  842.             }

  843.             value[i].data += 5;
  844.             value[i].len -= 5;

  845.             if (value[i].len == 0) {
  846.                 return "no value for \"path\"";
  847.             }

  848.             stcf->cookie_path.len = sizeof("; path=") - 1 + value[i].len;

  849.             stcf->cookie_path.data = ngx_pnalloc(cf->pool,
  850.                                                  stcf->cookie_path.len);
  851.             if (stcf->cookie_path.data == NULL) {
  852.                 return NGX_CONF_ERROR;
  853.             }

  854.             p = ngx_cpymem(stcf->cookie_path.data,
  855.                            "; path=", sizeof("; path=") - 1);
  856.             ngx_memcpy(p, value[i].data, value[i].len);


  857.         } else if (ngx_strncmp(value[i].data, "expires=", 8) == 0) {

  858.             if (stcf->cookie_expires != (time_t) NGX_CONF_UNSET) {
  859.                 return "parameter \"expires\" is duplicate";
  860.             }

  861.             value[i].data += 8;
  862.             value[i].len -= 8;

  863.             if (ngx_strcmp(value[i].data, "max") == 0) {
  864.                 stcf->cookie_expires = NGX_HTTP_STICKY_COOKIE_MAX_EXPIRES;

  865.             } else {
  866.                 stcf->cookie_expires = ngx_parse_time(&value[i], 1);
  867.                 if (stcf->cookie_expires == (time_t) NGX_ERROR) {
  868.                     return "invalid \"expires\" parameter value";
  869.                 }
  870.             }

  871.         } else if (ngx_strcmp(value[i].data, "httponly") == 0) {

  872.             if (stcf->cookie_httponly) {
  873.                 return "parameter \"httponly\" is duplicate";
  874.             }

  875.             stcf->cookie_httponly = 1;

  876.         } else if (ngx_strcmp(value[i].data, "secure") == 0) {

  877.             if (stcf->cookie_secure) {
  878.                 return "parameter \"secure\" is duplicate";
  879.             }

  880.             stcf->cookie_secure = 1;

  881.         } else if (ngx_strncmp(value[i].data, "samesite=", 9) == 0) {

  882.             if (stcf->cookie_samesite) {
  883.                 return "parameter \"samesite\" is duplicate";
  884.             }

  885.             value[i].data += 9;
  886.             value[i].len -= 9;

  887.             ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

  888.             stcf->cookie_samesite = ngx_palloc(cf->pool,
  889.                                              sizeof(ngx_http_complex_value_t));
  890.             if (stcf->cookie_samesite == NULL) {
  891.                 return NGX_CONF_ERROR;
  892.             }

  893.             ccv.cf = cf;
  894.             ccv.value = &value[i];
  895.             ccv.complex_value = stcf->cookie_samesite;

  896.             if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
  897.                 return NGX_CONF_ERROR;
  898.             }

  899.             if (stcf->cookie_samesite->lengths == NULL
  900.                 && ngx_http_upstream_sticky_samesite(&value[i]) != NGX_OK)
  901.             {
  902.                 return "invalid \"samesite\" parameter value";
  903.             }

  904.         } else {
  905.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  906.                                "unknown parameter \"%V\"", &value[i]);
  907.             return NGX_CONF_ERROR;
  908.         }
  909.     }

  910.     name.len = sizeof("cookie_") - 1  + stcf->cookie_name.len;
  911.     name.data = ngx_pnalloc(cf->pool, name.len);
  912.     if (name.data == NULL) {
  913.          return NGX_CONF_ERROR;
  914.     }

  915.     ngx_sprintf(name.data, "cookie_%V", &stcf->cookie_name);

  916.     index = ngx_http_get_variable_index(cf, &name);
  917.     if (index == NGX_ERROR) {
  918.         return NGX_CONF_ERROR;
  919.     }

  920.     indexp = ngx_array_push(stcf->lookup_vars);
  921.     if (indexp == NULL) {
  922.         return NGX_CONF_ERROR;
  923.     }

  924.     *indexp = index;

  925.     return NGX_CONF_OK;
  926. }


  927. static char *
  928. ngx_http_upstream_sticky_learn(ngx_conf_t *cf,
  929.     ngx_http_upstream_sticky_srv_conf_t *stcf, ngx_http_upstream_srv_conf_t *us)
  930. {
  931.     u_char                           *p;
  932.     ssize_t                           zone_size;
  933.     ngx_str_t                        *value, name, size;
  934.     ngx_int_t                         index, *indexp;
  935.     ngx_uint_t                        i;
  936.     ngx_msec_t                        timeout;
  937.     ngx_shm_zone_t                   *shm_zone;
  938.     ngx_http_upstream_sticky_sess_t  *sess;

  939.     zone_size = 0;
  940.     timeout = NGX_CONF_UNSET_MSEC;

  941.     stcf->create_vars = ngx_array_create(cf->pool, 1, sizeof(ngx_int_t));
  942.     if (stcf->create_vars == NULL) {
  943.         return NGX_CONF_ERROR;
  944.     }

  945.     value = cf->args->elts;

  946.     for (i = 2; i < cf->args->nelts; i++) {

  947.         if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {

  948.             if (zone_size != 0) {
  949.                 return "duplicate zone";
  950.             }

  951.             name.data = value[i].data + 5;

  952.             p = (u_char *) ngx_strchr(name.data, ':');

  953.             if (p == NULL) {
  954.                 return "zone size is not specified";
  955.             }

  956.             name.len = p - name.data;

  957.             if (name.len == 0) {
  958.                 return "zone name is not specified";
  959.             }

  960.             size.data = ++p;
  961.             size.len = value[i].data + value[i].len - p;

  962.             zone_size = ngx_parse_size(&size);
  963.             if (zone_size == NGX_ERROR) {
  964.                 return "invalid zone size";
  965.             }

  966.             /* 32k ~ 200 sessions, 1m ~ 8000 sessions */
  967.             if (zone_size < (ssize_t) (8 * ngx_pagesize)) {
  968.                 return "zone is too small";
  969.             }

  970.         } else if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) {

  971.             if (timeout != NGX_CONF_UNSET_MSEC) {
  972.                 return "duplicate timeout";
  973.             }

  974.             value[i].data += 8;
  975.             value[i].len -= 8;

  976.             timeout = ngx_parse_time(&value[i], 0);
  977.             if (timeout == (ngx_msec_t) NGX_ERROR || timeout == 0) {
  978.                 return "invalid timeout";
  979.             }

  980.         } else if (ngx_strncmp(value[i].data, "create=", 7) == 0) {

  981.             if (value[i].data[7] != '$') {
  982.                 return "missing variable in the \"create\" parameter";
  983.             }

  984.             value[i].data += 8;
  985.             value[i].len -= 8;

  986.             index = ngx_http_get_variable_index(cf, &value[i]);
  987.             if (index == NGX_ERROR) {
  988.                 return NGX_CONF_ERROR;
  989.             }

  990.             indexp = ngx_array_push(stcf->create_vars);
  991.             if (indexp == NULL) {
  992.                 return NGX_CONF_ERROR;
  993.             }

  994.             *indexp = index;

  995.         } else if (ngx_strncmp(value[i].data, "lookup=", 7) == 0) {

  996.             if (value[i].data[7] != '$') {
  997.                 return "missing variable in the \"lookup\" parameter";
  998.             }

  999.             value[i].data += 8;
  1000.             value[i].len -= 8;

  1001.             index = ngx_http_get_variable_index(cf, &value[i]);
  1002.             if (index == NGX_ERROR) {
  1003.                 return NGX_CONF_ERROR;
  1004.             }

  1005.             indexp = ngx_array_push(stcf->lookup_vars);
  1006.             if (indexp == NULL) {
  1007.                 return NGX_CONF_ERROR;
  1008.             }

  1009.             *indexp = index;

  1010.         } else if (ngx_strcmp(value[i].data, "header") == 0) {
  1011.             stcf->learn_after_headers = 1;

  1012.         } else {
  1013.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  1014.                                "unknown parameter \"%V\"", &value[i]);
  1015.             return NGX_CONF_ERROR;
  1016.         }
  1017.     }

  1018.     if (stcf->lookup_vars->nelts == 0) {
  1019.         return "\"lookup\" parameter is not specified";
  1020.     }

  1021.     if (stcf->create_vars->nelts == 0) {
  1022.         return "\"create\" parameter is not specified";
  1023.     }

  1024.     if (zone_size == 0) {
  1025.         return "\"zone\" parameter is not specified";
  1026.     }

  1027.     if (timeout == NGX_CONF_UNSET_MSEC) {
  1028.         timeout = 600000; /* 10m */
  1029.     }

  1030.     shm_zone = ngx_shared_memory_add(cf, &name, zone_size,
  1031.                                      &ngx_http_upstream_sticky_module);
  1032.     if (shm_zone == NULL) {
  1033.         return NGX_CONF_ERROR;
  1034.     }

  1035.     if (shm_zone->data) {
  1036.         sess = shm_zone->data;

  1037.         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  1038.                            "sticky zone \"%V\" is already used in "
  1039.                            "upstream \"%V\"", &name, sess->host);

  1040.         return NGX_CONF_ERROR;
  1041.     }

  1042.     sess = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_sticky_sess_t));
  1043.     if (sess == NULL) {
  1044.         return NGX_CONF_ERROR;
  1045.     }

  1046.     sess->timeout = timeout;
  1047.     sess->host = &us->host;

  1048.     sess->event.data = sess;
  1049.     sess->event.log = &cf->cycle->new_log;
  1050.     sess->event.handler = ngx_http_upstream_sticky_sess_timer_handler;
  1051.     sess->event.cancelable = 1;

  1052.     shm_zone->init = ngx_http_upstream_sticky_sess_init_zone;
  1053.     shm_zone->data = sess;

  1054.     stcf->shm_zone = shm_zone;

  1055.     return NGX_CONF_OK;
  1056. }


  1057. static ngx_int_t
  1058. ngx_http_upstream_sticky_init_worker(ngx_cycle_t *cycle)
  1059. {
  1060.     ngx_msec_t                             wait;
  1061.     ngx_uint_t                             i;
  1062.     ngx_http_upstream_srv_conf_t         **uscfp;
  1063.     ngx_http_upstream_main_conf_t         *umcf;
  1064.     ngx_http_upstream_sticky_sess_t       *sess;
  1065.     ngx_http_upstream_sticky_srv_conf_t   *stcf;

  1066.     if ((ngx_process != NGX_PROCESS_WORKER || ngx_worker != 0)
  1067.         && ngx_process != NGX_PROCESS_SINGLE)
  1068.     {
  1069.         return NGX_OK;
  1070.     }

  1071.     umcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_upstream_module);

  1072.     if (umcf == NULL) {
  1073.         return NGX_OK;
  1074.     }

  1075.     uscfp = umcf->upstreams.elts;

  1076.     for (i = 0; i < umcf->upstreams.nelts; i++) {

  1077.         if (uscfp[i]->srv_conf == NULL) {
  1078.             continue;
  1079.         }

  1080.         stcf = ngx_http_conf_upstream_srv_conf(uscfp[i],
  1081.                                                ngx_http_upstream_sticky_module);

  1082.         if (stcf == NULL || stcf->shm_zone == NULL) {
  1083.             continue;
  1084.         }

  1085.         sess = stcf->shm_zone->data;

  1086.         ngx_shmtx_lock(&sess->shpool->mutex);

  1087.         wait = ngx_http_upstream_sticky_sess_expire(sess, 0);

  1088.         ngx_shmtx_unlock(&sess->shpool->mutex);

  1089.         if (wait > 0) {
  1090.             ngx_add_timer(&sess->event, wait);
  1091.         }
  1092.     }

  1093.     return NGX_OK;
  1094. }