MEDIUM: stick-tables: Add srvkey option to stick-table

This allows using the address of the server rather than the name of the
server for keeping track of servers in a backend for stickiness.

The peers code was also extended to support feeding the dictionary using
this key instead of the name.

Fixes #814
This commit is contained in:
Thayne McCombs 2020-11-20 01:28:26 -07:00 committed by Willy Tarreau
parent dc38bc4a1a
commit 92149f9a82
15 changed files with 216 additions and 26 deletions

View File

@ -10647,7 +10647,7 @@ stick store-request <pattern> [table <table>] [{if | unless} <condition>]
stick-table type {ip | integer | string [len <length>] | binary [len <length>]}
size <size> [expire <expire>] [nopurge] [peers <peersect>]
size <size> [expire <expire>] [nopurge] [peers <peersect>] [srvkey <srvkey>]
[store <data_type>]*
Configure the stickiness table for the current section
May be used in sections : defaults | frontend | listen | backend
@ -10724,6 +10724,16 @@ stick-table type {ip | integer | string [len <length>] | binary [len <length>]}
be removed once full. Be sure not to use the "nopurge" parameter
if not expiration delay is specified.
<srvkey> specifies how each server is identified for the purposes of the
stick table. The valid values are "name" and "addr". If "name" is
given, then <name> argument for the server (may be generated by
a template). If "addr" is given, then the server is identified
by its current network address, including the port. "addr" is
especially useful if you are using service discovery to generate
the addresses for servers with peered stick-tables and want
to consistently use the same host across peers for a stickiness
token.
<data_type> is used to store additional information in the stick-table. This
may be used by ACLs in order to control various criteria related
to the activity of the client matching the stick-table. For each

View File

@ -31,5 +31,6 @@
struct dict *new_dict(const char *name);
struct dict_entry *dict_insert(struct dict *d, char *str);
void dict_entry_unref(struct dict *d, struct dict_entry *de);
#endif /* _HAPROXY_DICT_H */

View File

@ -424,6 +424,7 @@ struct proxy {
char *lfsd_file; /* file name where the structured-data logformat string for RFC5424 appears (strdup) */
int lfsd_line; /* file name where the structured-data logformat string for RFC5424 appears */
} conf; /* config information */
struct eb_root used_server_addr; /* list of server addresses in use */
void *parent; /* parent of the proxy when applicable */
struct comp *comp; /* http compression */

View File

@ -342,6 +342,7 @@ struct server {
struct ebpt_node name; /* place in the tree of used names */
int line; /* line where the section appears */
} conf; /* config information */
struct ebpt_node addr_node; /* Node for string representation of address for the server (including port number) */
/* Template information used only for server objects which
* serve as template filled at parsing time and used during
* server allocations from server templates.

View File

@ -38,7 +38,7 @@
__decl_thread(extern HA_SPINLOCK_T idle_conn_srv_lock);
extern struct eb_root idle_conn_srv;
extern struct task *idle_conn_task;
extern struct dict server_name_dict;
extern struct dict server_key_dict;
int srv_downtime(const struct server *s);
int srv_lastsession(const struct server *s);

View File

@ -56,7 +56,7 @@ enum {
STKTABLE_DT_BYTES_OUT_RATE,/* bytes rate from servers to client */
STKTABLE_DT_GPC1, /* General Purpose Counter 1 (unsigned 32-bit integer) */
STKTABLE_DT_GPC1_RATE, /* General Purpose Counter 1's event rate */
STKTABLE_DT_SERVER_NAME, /* The server name */
STKTABLE_DT_SERVER_KEY, /* The server key */
STKTABLE_STATIC_DATA_TYPES,/* number of types above */
/* up to STKTABLE_EXTRA_DATA_TYPES types may be registered here, always
* followed by the number of data types, must always be last.
@ -80,6 +80,12 @@ enum {
ARG_T_DELAY, /* a delay which supports time units */
};
/* They types of keys that servers can be identified by */
enum {
STKTABLE_SRV_NAME = 0,
STKTABLE_SRV_ADDR,
};
/* stick table key type flags */
#define STK_F_CUSTOM_KEYSIZE 0x00000001 /* this table's key size is configurable */
@ -112,7 +118,7 @@ union stktable_data {
/* types of each storable data */
int server_id;
struct dict_entry *server_name;
struct dict_entry *server_key;
unsigned int gpt0;
unsigned int gpc0;
struct freq_ctr_period gpc0_rate;
@ -188,6 +194,7 @@ struct stktable {
} peers;
unsigned long type; /* type of table (determines key format) */
unsigned int server_key_type; /* What type of key is used to identify servers */
size_t key_size; /* size of a key, maximum size in case of string */
unsigned int size; /* maximum number of sticky sessions in table */
unsigned int current; /* number of sticky sessions currently in table */

View File

@ -245,6 +245,19 @@ struct sockaddr_storage *str2sa_range(const char *str, int *port, int *low, int
struct protocol **proto, char **err,
const char *pfx, char **fqdn, unsigned int opts);
/* converts <addr> and <port> into a string representation of the address and port. This is sort
* of an inverse of str2sa_range, with some restrictions. The supported families are AF_INET,
* AF_INET6, AF_UNIX, and AF_CUST_SOCKPAIR. If the family is unsopported NULL is returned.
* If map_ports is true, then the sign of the port is included in the output, to indicate it is
* relative to the incoming port. AF_INET and AF_INET6 will be in the form "<addr>:<port>".
* AF_UNIX will either be just the path (if using a pathname) or "abns@<path>" if it is abstract.
* AF_CUST_SOCKPAIR will be of the form "sockpair@<fd>".
*
* The returned char* is allocated, and it is the responsibility of the caller to free it.
*/
char *sa2str(const struct sockaddr_storage *addr, int port, int map_ports);
/* converts <str> to a struct in_addr containing a network mask. It can be
* passed in dotted form (255.255.255.0) or in CIDR form (24). It returns 1
* if the conversion succeeds otherwise zero.

View File

@ -457,6 +457,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
curproxy->grace = defproxy.grace;
curproxy->conf.used_listener_id = EB_ROOT;
curproxy->conf.used_server_id = EB_ROOT;
curproxy->used_server_addr = EB_ROOT_UNIQUE;
if (defproxy.check_path)
curproxy->check_path = strdup(defproxy.check_path);

View File

@ -2682,7 +2682,7 @@ int check_config_validity()
free((void *)mrule->table.name);
mrule->table.t = target;
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_ID, NULL);
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_NAME, NULL);
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_KEY, NULL);
if (!in_proxies_list(target->proxies_list, curproxy)) {
curproxy->next_stkt_ref = target->proxies_list;
target->proxies_list = curproxy;
@ -2720,7 +2720,7 @@ int check_config_validity()
free((void *)mrule->table.name);
mrule->table.t = target;
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_ID, NULL);
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_NAME, NULL);
stktable_alloc_data_type(target, STKTABLE_DT_SERVER_KEY, NULL);
if (!in_proxies_list(target->proxies_list, curproxy)) {
curproxy->next_stkt_ref = target->proxies_list;
target->proxies_list = curproxy;

View File

@ -87,8 +87,10 @@ struct dict_entry *dict_insert(struct dict *d, char *s)
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock);
de = __dict_lookup(d, s);
HA_RWLOCK_RDUNLOCK(DICT_LOCK, &d->rwlock);
if (de)
if (de) {
HA_ATOMIC_ADD(&de->refcount, 1);
return de;
}
de = new_dict_entry(s);
if (!de)
@ -105,3 +107,23 @@ struct dict_entry *dict_insert(struct dict *d, char *s)
return de;
}
/*
* Unreference a dict entry previously acquired with <dict_insert>.
* If this is the last live reference to the entry, it is
* removed from the dictionary.
*/
void dict_entry_unref(struct dict *d, struct dict_entry *de)
{
if (!de)
return;
if (HA_ATOMIC_SUB(&de->refcount, 1) != 0)
return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
free_dict_entry(de);
}

View File

@ -1633,13 +1633,16 @@ static int peer_treat_updatemsg(struct appctx *appctx, struct peer *p, int updt,
chunk->area[chunk->data] = '\0';
*msg_cur += value_len;
de = dict_insert(&server_name_dict, chunk->area);
de = dict_insert(&server_key_dict, chunk->area);
dict_entry_unref(&server_key_dict, dc->rx[id - 1].de);
dc->rx[id - 1].de = de;
}
if (de) {
data_ptr = stktable_data_ptr(st->table, ts, data_type);
if (data_ptr)
if (data_ptr) {
HA_ATOMIC_ADD(&de->refcount, 1);
stktable_data_cast(data_ptr, std_t_dict) = de;
}
}
break;
}
@ -3059,6 +3062,8 @@ static inline void flush_dcache(struct peer *peer)
for (i = 0; i < dc->max_entries; i++) {
ebpt_delete(&dc->tx->entries[i]);
dc->tx->entries[i].key = NULL;
dict_entry_unref(&server_key_dict, dc->rx[i].de);
dc->rx[i].de = NULL;
}
dc->tx->prev_lookup = NULL;
dc->tx->lru_key = 0;

View File

@ -65,8 +65,8 @@ struct eb_root idle_conn_srv = EB_ROOT;
struct task *idle_conn_task = NULL;
/* The server names dictionary */
struct dict server_name_dict = {
.name = "server names",
struct dict server_key_dict = {
.name = "server keys",
.values = EB_ROOT_UNIQUE,
};
@ -193,6 +193,36 @@ void srv_set_dyncookie(struct server *s)
HA_RWLOCK_RDUNLOCK(PROXY_LOCK, &p->lock);
}
/*
* Must be called with the server lock held, and will write-lock the proxy.
*/
static void srv_set_addr_desc(struct server *s)
{
struct proxy *p = s->proxy;
char *key;
key = sa2str(&s->addr, s->svc_port, s->flags & SRV_F_MAPPORTS);
if (s->addr_node.key) {
if (strcmp(key, s->addr_node.key) == 0) {
free(key);
return;
}
HA_RWLOCK_WRLOCK(PROXY_LOCK, &p->lock);
ebpt_delete(&s->addr_node);
HA_RWLOCK_WRUNLOCK(PROXY_LOCK, &p->lock);
free(s->addr_node.key);
}
s->addr_node.key = key;
HA_RWLOCK_WRLOCK(PROXY_LOCK, &p->lock);
ebis_insert(&p->used_server_addr, &s->addr_node);
HA_RWLOCK_WRUNLOCK(PROXY_LOCK, &p->lock);
}
/*
* Registers the server keyword list <kwl> as a list of valid keywords for next
* parsing sessions.
@ -2055,6 +2085,9 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
newsrv->addr = *sk;
newsrv->svc_port = port;
// we don't need to lock the server here, because
// we are in the process of initializing
srv_set_addr_desc(newsrv);
if (!newsrv->srvrq && !newsrv->hostname && !protocol_by_family(newsrv->addr.ss_family)) {
ha_alert("parsing [%s:%d] : Unknown protocol family %d '%s'\n",
@ -3522,6 +3555,7 @@ int update_server_addr(struct server *s, void *ip, int ip_sin_family, const char
break;
};
srv_set_dyncookie(s);
srv_set_addr_desc(s);
return 0;
}
@ -3694,6 +3728,7 @@ out:
/* force connection cleanup on the given server */
srv_cleanup_connections(s);
srv_set_dyncookie(s);
srv_set_addr_desc(s);
}
if (updater)
chunk_appendf(msg, " by '%s'", updater);
@ -4174,6 +4209,7 @@ static int srv_iterate_initaddr(struct server *srv)
return return_code;
out:
srv_set_dyncookie(srv);
srv_set_addr_desc(srv);
return return_code;
}

View File

@ -22,6 +22,7 @@
#include <haproxy/arg.h>
#include <haproxy/cfgparse.h>
#include <haproxy/cli.h>
#include <haproxy/dict.h>
#include <haproxy/errors.h>
#include <haproxy/global.h>
#include <haproxy/http_rules.h>
@ -94,6 +95,12 @@ void __stksess_free(struct stktable *t, struct stksess *ts)
*/
void stksess_free(struct stktable *t, struct stksess *ts)
{
void *data;
data = stktable_data_ptr(t, ts, STKTABLE_DT_SERVER_KEY);
if (data) {
dict_entry_unref(&server_key_dict, stktable_data_cast(data, server_key));
stktable_data_cast(data, server_key) = NULL;
}
HA_SPIN_LOCK(STK_TABLE_LOCK, &t->lock);
__stksess_free(t, ts);
HA_SPIN_UNLOCK(STK_TABLE_LOCK, &t->lock);
@ -877,6 +884,25 @@ int parse_stick_table(const char *file, int linenum, char **args,
}
idx++;
}
else if (strcmp(args[idx], "srvkey") == 0) {
char *keytype;
idx++;
keytype = args[idx];
if (strcmp(keytype, "name") == 0) {
t->server_key_type = STKTABLE_SRV_NAME;
}
else if (strcmp(keytype, "addr") == 0) {
t->server_key_type = STKTABLE_SRV_ADDR;
}
else {
ha_alert("parsing [%s:%d] : %s : unknown server key type '%s'.\n",
file, linenum, args[0], keytype);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
idx++;
}
else {
ha_alert("parsing [%s:%d] : %s: unknown argument '%s'.\n",
file, linenum, args[0], args[idx]);
@ -1048,7 +1074,7 @@ struct stktable_data_type stktable_data_types[STKTABLE_DATA_TYPES] = {
[STKTABLE_DT_BYTES_OUT_RATE]= { .name = "bytes_out_rate", .std_type = STD_T_FRQP, .arg_type = ARG_T_DELAY },
[STKTABLE_DT_GPC1] = { .name = "gpc1", .std_type = STD_T_UINT },
[STKTABLE_DT_GPC1_RATE] = { .name = "gpc1_rate", .std_type = STD_T_FRQP, .arg_type = ARG_T_DELAY },
[STKTABLE_DT_SERVER_NAME] = { .name = "server_name", .std_type = STD_T_DICT },
[STKTABLE_DT_SERVER_KEY] = { .name = "server_key", .std_type = STD_T_DICT },
};
/* Registers stick-table extra data type with index <idx>, name <name>, type
@ -1095,6 +1121,9 @@ int stktable_get_data_type(char *name)
if (strcmp(name, stktable_data_types[type].name) == 0)
return type;
}
/* For backwards compatibility */
if (strcmp(name, "server_name") == 0)
return STKTABLE_DT_SERVER_KEY;
return -1;
}

View File

@ -1202,17 +1202,27 @@ static inline void sticking_rule_find_target(struct stream *s,
/* Look for the server name previously stored in <t> stick-table */
HA_RWLOCK_RDLOCK(STK_SESS_LOCK, &ts->lock);
ptr = __stktable_data_ptr(t, ts, STKTABLE_DT_SERVER_NAME);
de = stktable_data_cast(ptr, server_name);
ptr = __stktable_data_ptr(t, ts, STKTABLE_DT_SERVER_KEY);
de = stktable_data_cast(ptr, server_key);
HA_RWLOCK_RDUNLOCK(STK_SESS_LOCK, &ts->lock);
if (de) {
struct ebpt_node *name;
struct ebpt_node *node;
name = ebis_lookup(&px->conf.used_server_name, de->value.key);
if (name) {
srv = container_of(name, struct server, conf.name);
goto found;
if (t->server_key_type == STKTABLE_SRV_NAME) {
node = ebis_lookup(&px->conf.used_server_name, de->value.key);
if (node) {
srv = container_of(node, struct server, conf.name);
goto found;
}
} else if (t->server_key_type == STKTABLE_SRV_ADDR) {
HA_RWLOCK_RDLOCK(PROXY_LOCK, &px->lock);
node = ebis_lookup(&px->used_server_addr, de->value.key);
HA_RWLOCK_RDUNLOCK(PROXY_LOCK, &px->lock);
if (node) {
srv = container_of(node, struct server, addr_node);
goto found;
}
}
}
@ -1378,7 +1388,9 @@ static int process_store_rules(struct stream *s, struct channel *rep, int an_bit
for (i = 0; i < s->store_count; i++) {
struct stksess *ts;
void *ptr;
char *key;
struct dict_entry *de;
struct stktable *t = s->store[i].table;
if (objt_server(s->target) && objt_server(s->target)->flags & SRV_F_NON_STICK) {
stksess_free(s->store[i].table, s->store[i].ts);
@ -1386,27 +1398,34 @@ static int process_store_rules(struct stream *s, struct channel *rep, int an_bit
continue;
}
ts = stktable_set_entry(s->store[i].table, s->store[i].ts);
ts = stktable_set_entry(t, s->store[i].ts);
if (ts != s->store[i].ts) {
/* the entry already existed, we can free ours */
stksess_free(s->store[i].table, s->store[i].ts);
stksess_free(t, s->store[i].ts);
}
s->store[i].ts = NULL;
HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
ptr = __stktable_data_ptr(s->store[i].table, ts, STKTABLE_DT_SERVER_ID);
ptr = __stktable_data_ptr(t, ts, STKTABLE_DT_SERVER_ID);
stktable_data_cast(ptr, server_id) = __objt_server(s->target)->puid;
HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
if (t->server_key_type == STKTABLE_SRV_NAME)
key = __objt_server(s->target)->id;
else if (t->server_key_type == STKTABLE_SRV_ADDR)
key = __objt_server(s->target)->addr_node.key;
else
continue;
HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
de = dict_insert(&server_name_dict, __objt_server(s->target)->id);
de = dict_insert(&server_key_dict, key);
if (de) {
ptr = __stktable_data_ptr(s->store[i].table, ts, STKTABLE_DT_SERVER_NAME);
stktable_data_cast(ptr, server_name) = de;
ptr = __stktable_data_ptr(t, ts, STKTABLE_DT_SERVER_KEY);
stktable_data_cast(ptr, server_key) = de;
}
HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
stktable_touch_local(s->store[i].table, ts, 1);
stktable_touch_local(t, ts, 1);
}
s->store_count = 0; /* everything is stored */

View File

@ -1214,6 +1214,51 @@ struct sockaddr_storage *str2sa_range(const char *str, int *port, int *low, int
return ret;
}
/* converts <addr> and <port> into a string representation of the address and port. This is sort
* of an inverse of str2sa_range, with some restrictions. The supported families are AF_INET,
* AF_INET6, AF_UNIX, and AF_CUST_SOCKPAIR. If the family is unsopported NULL is returned.
* If map_ports is true, then the sign of the port is included in the output, to indicate it is
* relative to the incoming port. AF_INET and AF_INET6 will be in the form "<addr>:<port>".
* AF_UNIX will either be just the path (if using a pathname) or "abns@<path>" if it is abstract.
* AF_CUST_SOCKPAIR will be of the form "sockpair@<fd>".
*
* The returned char* is allocated, and it is the responsibility of the caller to free it.
*/
char * sa2str(const struct sockaddr_storage *addr, int port, int map_ports)
{
char buffer[INET6_ADDRSTRLEN];
char *out = NULL;
const void *ptr;
const char *path;
switch (addr->ss_family) {
case AF_INET:
ptr = &((struct sockaddr_in *)addr)->sin_addr;
break;
case AF_INET6:
ptr = &((struct sockaddr_in6 *)addr)->sin6_addr;
break;
case AF_UNIX:
path = ((struct sockaddr_un *)addr)->sun_path;
if (path[0] == '\0') {
const int max_length = sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path) - 1;
return memprintf(&out, "abns@%.*s", max_length, path+1);
} else {
return strdup(path);
}
case AF_CUST_SOCKPAIR:
return memprintf(&out, "sockpair@%d", ((struct sockaddr_in *)addr)->sin_addr.s_addr);
default:
return NULL;
}
inet_ntop(addr->ss_family, ptr, buffer, get_addr_len(addr));
if (map_ports)
return memprintf(&out, "%s:%+d", buffer, port);
else
return memprintf(&out, "%s:%d", buffer, port);
}
/* converts <str> to a struct in_addr containing a network mask. It can be
* passed in dotted form (255.255.255.0) or in CIDR form (24). It returns 1
* if the conversion succeeds otherwise zero.