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);