From 8f0228551bc690809f1c96ac5aa2bf056916dcc2 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Fri, 3 Mar 2023 09:26:47 +0200 Subject: [PATCH] Identify listeners and services by their names (labels) as well as indices * src/config.c (parse_listen_http) (parse_listen_https): Handle optional listener name. Make sure it is unique. (parse_service): Make sure service name is unique. * src/pound.h (LISTENER): New field: name. * src/poundctl.tmpl: Show listener name, if available. * svc.c (get_nth_listener, get_nth_service) (get_nth_backend,ctl_getnum): Remove. (IDENT): New data type. (ctl_getident,locate_listener,locate_service) (locate_backend): New functions. (service_serialize): Represent NULL service name as null. (listener_serialize): Add listener name. (ctl_service,ctl_listener): Identify objects by their ids or numbers. * NEWS: Document changes. * doc/pound.8: Likewise. * doc/poundctl.8: Likewise. --- NEWS | 29 +++++- doc/pound.8 | 41 ++++++--- doc/poundctl.8 | 20 ++++- src/config.c | 65 +++++++++++++- src/pound.h | 1 + src/poundctl.tmpl | 6 +- src/svc.c | 218 ++++++++++++++++++++++++++++------------------ 7 files changed, 277 insertions(+), 103 deletions(-) diff --git a/NEWS b/NEWS index e7c30b2..e7f852e 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Pound -- history of user-visible changes. 2023-02-20 +Pound -- history of user-visible changes. 2023-03-03 See the end of file for copying conditions. Pound is a continuation of the software originally developed by @@ -113,6 +113,33 @@ Request accessor can be used in all strings where the use of backreferences is allowed: i.e. arguments to Redirect, ACME, Error directives, and to all request modification directives described above. + +* Listener labels + +Listeners can be assigned symbolic labels. The syntax is: + + ListenHTTP "name" +or + ListenHTTPS "name" + +The "name" must be unique among all listeners defined in the +configuration. + +This symbolic name can be used to identify listener in poundctl +requests (see below). + +* Service labels + +Service labels must be unique among all services within the +listener (or in the configuration file, for global ones). + +* Use of listener and service labels in poundctl + +Listeners and services can be identified both by their numbers and +labels. For example: + + poundctl list /main/static/1 + Version 4.5, 2023-02-12 diff --git a/doc/pound.8 b/doc/pound.8 index 3e4ee5d..048d77b 100644 --- a/doc/pound.8 +++ b/doc/pound.8 @@ -14,7 +14,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with pound. If not, see . -.TH POUND 8 "February 19, 2023" "pound" "System Manager's Manual" +.TH POUND 8 "March 3, 2023" "pound" "System Manager's Manual" .SH NAME pound \- HTTP/HTTPS reverse-proxy and load-balancer .SH SYNOPSIS @@ -144,6 +144,13 @@ Normally, the connection between and .I backends is via plain HTTP. It is, however, possible to use HTTPS as well. +.PP +A working +.B pound +configuration may define multiple listeners and services. They can be +identified either by 0-based ordinal number within the configuration +(or, for services, within the listener) or by their symbolic name. +Backends are identified by their ordinal number within service. .SH WORKER MODEL Each incoming request is processed by a specific \fIworker\fR, i.e. a thread in the running program. The number of running workers is @@ -549,12 +556,18 @@ the PID file will remain on disk after termination of the program. .SH "HTTP Listener" An HTTP listener defines an address and port that .B pound -will listen on for HTTP requests. All configuration directives enclosed -between +will listen on for HTTP requests. The listener declaration begins with +the keyword +.B ListenHTTP +on a separate line. The keyword may be followed by a quoted string +supplying listener name. This name is a unique label that identifies +the listener. +.PP +All configuration directives enclosed between .B ListenHTTP and .B End -are specific to a single HTTP listener. At the very least you must specify +are specific to a single HTTP listener. At the very least you must specify and address and a port for each listener. The following directives are available: .TP @@ -673,10 +686,12 @@ value. .TP \fBService\fR [ "name" ] This defines a private service (see below for service definition -syntax). This service will be used only by this listener. The service -may be optionally named, with the name showing in the +syntax). This service will be used only by this listener. Optional +\fIname\fR supplies the label, that can be used in .BR poundctl (8) -listings. +requests to identify the service. This label must be unique among all +services within the enclosing listener, or, for global services, +within the configuration. .TP \fBACME\fR "directory" Serve ACME challenge requests from the given @@ -777,8 +792,14 @@ Same as \fBDeleteHeader\fR. .SH "HTTPS Listener" An HTTPS listener defines an address and port that .B pound -will listen on for HTTPS requests. All configuration directives enclosed -between +will listen on for HTTPS requests. The listener declaration begins with +the keyword +.B ListenHTTPS +on a separate line. The keyword may be followed by a quoted string +supplying listener name. This name is a unique label that identifies +the listener. +.PP +All configuration directives enclosed between .B ListenHTTPS and .B End @@ -1124,7 +1145,7 @@ statement. Numbering of subexpressions starts at 1 (\fB$0\fR refers to the entire matching string). Numbering of matches starts at 0. .IP -For example, given the following statements +For example, given the following statements .IP .EX Host -re "www\\.(.+)" diff --git a/doc/poundctl.8 b/doc/poundctl.8 index 6163e2f..fa67f69 100644 --- a/doc/poundctl.8 +++ b/doc/poundctl.8 @@ -14,7 +14,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with pound. If not, see . -.TH POUNDCTL 8 "January 13, 2023" "poundctl" "System Manager's Manual" +.TH POUNDCTL 8 "March 3, 2023" "poundctl" "System Manager's Manual" .SH NAME poundctl \- control the pound daemon .SH SYNOPSIS @@ -62,8 +62,22 @@ All commands take the \fB/\fIL\fB/\fIS\fB/\fIB\fR argument, which specifies the .B pound object to apply the command to. Here, \fIL\fR, \fIS\fR, and \fIB\fR -stand for the numbers of \fIlistener\fR, \fIservice\fR and -\fIbackend\fR, correspondingly. Depending on the command, either +stand for the identifiers of \fIlistener\fR, \fIservice\fR and +\fIbackend\fR, correspondingly. For listeners and services, both +numeric identifiers or symbolic names are allowed. Numeric +identifiers refer to the ordinal number of the listener in the +configuration file, or service within the enclosing listener +(or in the configuration file, if \fIL\fR is \fB\-\fR, see below). +Symbolic names refer to the names assigned with the corresponding +.BR ListenHTTP , +.BR ListenHTTPS , +or +.B Service +statement in the configuration file. The identifier \fIB\fR is +always numeric and refers to the ordinal number of the backend in +the service. +.PP +Depending on the command, either \fIB\fR or both /\fIS\fR/\fIB\fR/ may be omitted. For example, the following command will disable backend 2 in service 1 of listener 0: .PP diff --git a/src/config.c b/src/config.c index d2047a0..07d113f 100644 --- a/src/config.c +++ b/src/config.c @@ -45,7 +45,7 @@ typedef unsigned TOKENMASK; #define T_MASK_ISSET(m,t) ((m) & T_BIT(t)) #define T_ANY 0 /* any token, inlcuding newline */ /* Unquoted character sequence */ -#define T_UNQ (T_BIT (T_IDENT) | T_BIT (T_NUMBER) | T_BIT(T_LITERAL)) +#define T_UNQ (T_BIT (T_IDENT) | T_BIT (T_NUMBER) | T_BIT (T_LITERAL)) /* Locations in the source file */ struct locus_point @@ -2650,6 +2650,18 @@ static PARSER_TABLE service_parsetab[] = { { NULL } }; +static int +find_service_ident (SERVICE_HEAD *svc_head, char const *name) +{ + SERVICE *svc; + SLIST_FOREACH (svc, svc_head, next) + { + if (svc->name && strcmp (svc->name, name) == 0) + return 1; + } + return 0; +} + static int parse_service (void *call_data, void *section_data) { @@ -2672,7 +2684,14 @@ parse_service (void *call_data, void *section_data) return PARSER_FAIL; if (tok->type == T_STRING) - svc->name = xstrdup (tok->str); + { + if (find_service_ident (head, tok->str)) + { + conf_error ("%s", "service name is not unique"); + return PARSER_FAIL; + } + svc->name = xstrdup (tok->str); + } else putback_tkn (tok); @@ -3055,6 +3074,18 @@ listener_alloc (POUND_DEFAULTS *dfl) return lst; } +static int +find_listener_ident (LISTENER_HEAD *list_head, char const *name) +{ + LISTENER *lstn; + SLIST_FOREACH (lstn, list_head, next) + { + if (lstn->name && strcmp (lstn->name, name) == 0) + return 1; + } + return 0; +} + static int parse_listen_http (void *call_data, void *section_data) { @@ -3062,10 +3093,25 @@ parse_listen_http (void *call_data, void *section_data) LISTENER_HEAD *list_head = call_data; POUND_DEFAULTS *dfl = section_data; struct locus_range range; + struct token *tok; if ((lst = listener_alloc (dfl)) == NULL) return PARSER_FAIL; + if ((tok = gettkn_any ()) == NULL) + return PARSER_FAIL; + else if (tok->type == T_STRING) + { + if (find_listener_ident (list_head, tok->str)) + { + conf_error ("%s", "listener name is not unique"); + return PARSER_FAIL; + } + lst->name = xstrdup (tok->str); + } + else + putback_tkn (tok); + if (parser_loop (http_parsetab, lst, section_data, &range)) return PARSER_FAIL; @@ -3585,10 +3631,25 @@ parse_listen_https (void *call_data, void *section_data) struct locus_range range; POUND_CTX *pc; struct stringbuf sb; + struct token *tok; if ((lst = listener_alloc (dfl)) == NULL) return PARSER_FAIL; + if ((tok = gettkn_any ()) == NULL) + return PARSER_FAIL; + else if (tok->type == T_STRING) + { + if (find_listener_ident (list_head, tok->str)) + { + conf_error ("%s", "listener name is not unique"); + return PARSER_FAIL; + } + lst->name = xstrdup (tok->str); + } + else + putback_tkn (tok); + lst->ssl_op_enable = SSL_OP_ALL; #ifdef SSL_OP_NO_COMPRESSION lst->ssl_op_enable |= SSL_OP_NO_COMPRESSION; diff --git a/src/pound.h b/src/pound.h index c3dfd8d..2c20441 100644 --- a/src/pound.h +++ b/src/pound.h @@ -613,6 +613,7 @@ typedef SLIST_HEAD (,_pound_ctx) POUND_CTX_HEAD; /* Listener definition */ typedef struct _listener { + char *name; /* symbolic name */ struct addrinfo addr; /* IPv4/6 address */ int sock; /* listening socket */ POUND_CTX_HEAD ctx_head; /* CTX for SSL connections */ diff --git a/src/poundctl.tmpl b/src/poundctl.tmpl index 0e31a12..2c5d8d5 100644 --- a/src/poundctl.tmpl +++ b/src/poundctl.tmpl @@ -54,12 +54,12 @@ Listeners: {{range $lno,$lstn = .listeners -}} {{printf "%3d" $lno}}. {{block "default.print_listener" $lstn -}} - Listener {{.protocol}}://{{.address}} {{if .enabled}}enabled{{else}}disabled{{end}} + Listener {{if .name}}"{{.name}}" {{end}}{{.protocol}}://{{.address}} {{if .enabled}}enabled{{else}}disabled{{end}} {{- /* For each listener, show all its services */ -}} {{block "default.print_services" .services}} {{- range $sno,$svc = .}} {{printf "%3d" $sno}}. {{block "default.print_service" $svc -}} - Service "{{.name}}" {{if .enabled}}active{{else}}disabled{{end}} ({{.tot_pri}}) + Service {{if .name}}"{{.name}}" {{end}}{{if .enabled}}active{{else}}disabled{{end}} ({{.tot_pri}}) {{- /* List each backend in service */ -}} {{- range $bno,$backend = .backends}} {{printf "%3d" $bno}}. {{block "default.print_backend" $backend -}} @@ -125,7 +125,7 @@ Global services: {{- /* Iterate over all listeners */ -}} {{- range $lno,$lstn = .listeners }} {{block "xml.print_listener" $lstn -}} - services : &services; - SLIST_FOREACH (svc, head, next) - { - if (n == 0) - break; - n--; - } - return svc; -} - -/* - * select a back-end by number - */ -static BACKEND * -get_nth_backend (SERVICE *svc, int n) -{ - BACKEND *be; - - SLIST_FOREACH (be, &svc->backends, next) - { - if (n == 0) - break; - n--; - } - return be; -} - static int session_backend_index (SESSION *sess) { @@ -1821,7 +1770,7 @@ service_serialize (SERVICE *svc) pthread_mutex_lock (&svc->mut); typename = sess_type_to_str (svc->sess_type); - if (json_object_set (obj, "name", json_new_string (svc->name ? svc->name : "")) + if ((svc->name && json_object_set (obj, "name", json_new_string (svc->name))) || json_object_set (obj, "enabled", json_new_bool (!svc->disabled)) || json_object_set (obj, "tot_pri", json_new_integer (svc->tot_pri)) || json_object_set (obj, "abs_pri", json_new_integer (svc->abs_pri)) @@ -1850,7 +1799,9 @@ listener_serialize (LISTENER *lstn) { int err = 0; - err = json_object_set (obj, "address", addrinfo_serialize (&lstn->addr)); + if (lstn->name) + err = json_object_set (obj, "name", json_new_string (lstn->name)); + err |= json_object_set (obj, "address", addrinfo_serialize (&lstn->addr)); is_https = !SLIST_EMPTY (&lstn->ctx_head); err |= json_object_set (obj, "protocol", @@ -2050,26 +2001,139 @@ control_list_all (BIO *c, char const *url) return rc; } -static int -ctl_getnum (char const **url) +/* + * Type of object identifier. + */ +enum + { + IDTYPE_ERR = -1, + IDTYPE_NUM, /* Numeric identifier (index). */ + IDTYPE_STR /* String identifier (name). */ + }; + +/* + * Object identifier. + */ +typedef struct { + int type; /* Identifier type. */ + union + { + int n; /* Index value (IDTYPE_NUM). */ + struct /* String value (IDTYPE_STR). */ + { + int len; /* Name length. */ + char const *name; /* Pointer to the name. */ + } s; + }; +} IDENT; + +/* + * Get single identifier from the URL (must point to a /, on entry). + * On success, advance URL to the point past the end of the identifier + * (normally next / or end of URL). + * On error, return ident with its type set to IDTYPE_ERR. + */ +static IDENT +ctl_getident (char const **url) +{ + IDENT retval = { IDTYPE_ERR }; long n; char *end; if (**url == '/') - ++*url; - else - return -1; - errno = 0; - n = strtol (*url, &end, 10); - if (errno || n > INT_MAX) - return -1; - if (*end != 0 && *end != '/' && *end != '?') - return -1; - *url = end; - return n; + { + ++*url; + errno = 0; + n = strtol (*url, &end, 10); + if (*end != 0 && *end != '/' && *end != '?') + { + end = (char*) *url + strcspn (*url, "/?"); + retval.s.name = *url; + retval.s.len = end - *url; + retval.type = IDTYPE_STR; + *url = end; + } + else if (errno == 0 && n <= INT_MAX) + { + retval.type = IDTYPE_NUM; + retval.n = n; + *url = end; + } + } + return retval; } +/* + * Locate listener identified by ID. + */ +static LISTENER * +locate_listener (IDENT id) +{ + LISTENER *lstn = NULL; + + if (id.type != IDTYPE_ERR) + { + long n = 0; + SLIST_FOREACH (lstn, &listeners, next) + { + if ((id.type == IDTYPE_NUM && n == id.n) || + (lstn->name && strlen (lstn->name) == id.s.len && + memcmp (lstn->name, id.s.name, id.s.len) == 0)) + break; + n++; + } + } + return lstn; +} + +/* + * Locate service identified by ID. If LSTN is null, look it up in the + * global service list. + */ +static SERVICE * +locate_service (LISTENER *lstn, IDENT id) +{ + SERVICE *svc = 0; + + if (id.type != IDTYPE_ERR) + { + int n = 0; + SERVICE_HEAD *head = lstn ? &lstn->services : &services; + + SLIST_FOREACH (svc, head, next) + { + if ((id.type == IDTYPE_NUM && n == id.n) || + (svc->name && strlen (svc->name) == id.s.len && + memcmp (svc->name, id.s.name, id.s.len) == 0)) + break; + n++; + } + } + return svc; +} + +/* + * Locate backend identified by ID. Only IDTYPE_NUM is supported. + */ +static BACKEND * +locate_backend (SERVICE *svc, IDENT id) +{ + BACKEND *be = NULL; + + if (id.type == IDTYPE_NUM) + { + long n = 0; + SLIST_FOREACH (be, &svc->backends, next) + { + if (n == id.n) + break; + n++; + } + } + return be; +} + enum object_type { OBJ_LISTENER, @@ -2093,12 +2157,9 @@ typedef int (*OBJHANDLER) (BIO *, OBJECT *, char const *, void *); static int ctl_backend (OBJHANDLER func, void *data, BIO *c, char const *url, SERVICE *svc) { - int n; OBJECT obj = { .type = OBJ_BACKEND }; - if ((n = ctl_getnum (&url)) < 0) - return HTTP_STATUS_NOT_FOUND; - if ((obj.be = get_nth_backend (svc, n)) == NULL) + if ((obj.be = locate_backend (svc, ctl_getident (&url))) == NULL) return HTTP_STATUS_NOT_FOUND; return func (c, &obj, url, data); } @@ -2106,13 +2167,9 @@ ctl_backend (OBJHANDLER func, void *data, BIO *c, char const *url, SERVICE *svc) static int ctl_service (OBJHANDLER func, void *data, BIO *c, char const *url, LISTENER *lstn) { - int n; OBJECT obj = { .type = OBJ_SERVICE }; - if ((n = ctl_getnum (&url)) < 0) - return HTTP_STATUS_NOT_FOUND; - - if ((obj.svc = get_nth_service (lstn, n)) == NULL) + if ((obj.svc = locate_service (lstn, ctl_getident (&url))) == NULL) return HTTP_STATUS_NOT_FOUND; if (*url && *url != '?') @@ -2123,7 +2180,6 @@ ctl_service (OBJHANDLER func, void *data, BIO *c, char const *url, LISTENER *lst static int ctl_listener (OBJHANDLER func, void *data, BIO *c, char const *url) { - int n; OBJECT obj = { .type = OBJ_LISTENER }; size_t len = strcspn (url, "?"); @@ -2137,14 +2193,8 @@ ctl_listener (OBJHANDLER func, void *data, BIO *c, char const *url) obj.lstn = NULL; url += 2; } - else - { - if ((n = ctl_getnum (&url)) < 0) - return HTTP_STATUS_NOT_FOUND; - - if ((obj.lstn = get_nth_listener (n)) == NULL) - return HTTP_STATUS_NOT_FOUND; - } + else if ((obj.lstn = locate_listener (ctl_getident (&url))) == NULL) + return HTTP_STATUS_NOT_FOUND; if (*url && *url != '?') return ctl_service (func, data, c, url, obj.lstn);