Download raw body.
add gotd.conf connection options
On 2023/01/02 16:52:15 +0100, Stefan Sperling <stsp@stsp.name> wrote:
> --- gotd/gotd.conf.5
> +++ gotd/gotd.conf.5
> @@ -31,6 +31,56 @@ The available global configuration directives are as f
> .Sh GLOBAL CONFIGURATION
> The available global configuration directives are as follows:
> .Bl -tag -width Ds
> +.It Ic connection Ar option
> +Set the specified options and limits for connections to the
> +.Xr gotd 8
> +unix socket.
> +.Pp
> +The
> +.Ic connection
> +directive may be specified multiple times, and multiple
> +.Ar option
> +arguments may be specified within curly braces:
> +.Pp
> +.Ic connection Brq Ar option1 Ar option2 ...
i'd drop "option1 option2". Other manpages (e.g. httpd.conf(5))
usually just get away with
.Ic connection Brq ...
> +.Pp
> +Each option should only be specified once.
> +If a given option is listed multiple times, the last line which sets this
> +option wins.
> +.Pp
> +Valid connection options are:
> +.Bl -tag -width Ds
> +.It Ic authentication timeout Ar seconds
> +Specify the timeout for client authentication.
> +If this timeout is exceeded due to an unexpected failure in the authentication
> +process, such as a crash, the connection will be terminated.
> +.Pp
> +The default timeout is 5 seconds.
> +This should only be changed if legitimate connections are exceeding the
> +default timeout for some reason.
To be fair I'm not convinced about this knob.
The auth process is just a getpeereuid() + a loop over the ACLs, it
shouldn't never hit the limit and if it does it's a bug. users should
report it rather than trying to crank up the limit.
I'm not against the internal timeout for the auth process, I just
don't think it's worth exposing it.
> +.It Ic request timeout Ar seconds
> +Specify the inactivity timeout for operations between client and server.
> +If this timeout is exceeded while a Git protocol request is being processed,
> +the request will be aborted and the connection will be terminated.
> +.Pp
> +The default timeout is 3600 seconds (1 hour).
> +This should only be changed if legitimate requests are exceeding the default
> +timeout for some reason, such as the server spending an extraordinary
> +amount of time generating a pack file.
> +.It Ic user Ar identity Ic max Ar number
> +Limit the maximum amount of concurrent connections by the user with
> +the username
> +.Ar identity
> +to
> +.Ar number .
> +Numeric user IDs are also accepted.
> +.Pp
> +The default per-user limit is 4.
> +This should only be changed if concurrent connections from a given user are
> +expected to exceed the default limit, for example if an anonymous user
> +is granted read access and many concurrent connections will share this
> +anonymous user identity.
> +.El
> .It Ic unix_socket Ar path
> Set the path to the unix socket which
> .Xr gotd 8
> @@ -161,6 +211,15 @@ repository "openbsd/ports" {
> permit ro anonymous
> deny flan_hacker
> }
> +
> +# Use a larger request timeout value:
> +connection request timeout 7200 # 2 hours
> +
> +# Some users are granted a higher concurrent connection limit:
> +connection {
> + user flan_hacker max 16
just wondering, what about adding a `limit' keyword there too, e.g.:
connection limit user flan_hacker max 16
connection {
limit user foo max 32
limit user bar max 64
}
It doesn't add anything, but it'd almost read as plain english then.
> + user anonymous max 32
> +}
> .Ed
> .Sh SEE ALSO
> .Xr got 1 ,
> blob - d17cc8a6c43063e47af1fb6b1ce517a12eb909c5
> blob + 5a527cb9b1db88f3a364c3ff9f274fa12f1d81e7
> --- gotd/gotd.h
> +++ gotd/gotd.h
> @@ -28,6 +28,9 @@
> #define GOTD_FD_NEEDED 6
> #define GOTD_FILENO_MSG_PIPE 3
>
> +#define GOTD_DEFAULT_REQUEST_TIMEOUT 3600
> +#define GOTD_DEFAULT_AUTH_TIMEOUT 5
> +
> /* Client hash tables need some extra room. */
> #define GOTD_CLIENT_TABLE_SIZE (GOTD_MAXCLIENTS * 4)
>
> @@ -109,6 +112,11 @@ struct gotd {
> size_t nids;
> };
>
> +struct gotd_uid_connection_limit {
> + uid_t uid;
> + int max_connections;
> +};
> +
> struct gotd {
> pid_t pid;
> char unix_socket_path[PATH_MAX];
> @@ -117,6 +125,10 @@ struct gotd {
> struct gotd_repolist repos;
> int nrepos;
> struct gotd_child_proc listen_proc;
> + struct timeval request_timeout;
> + struct timeval auth_timeout;
> + struct gotd_uid_connection_limit *connection_limits;
> + size_t nconnection_limits;
>
> char *argv0;
> const char *confpath;
> blob - bcc891f386e858648339da2b211be2df91530d4b
> blob + 0244dadfadb3fbe5d3d608eaee023f5df9744ce8
> --- gotd/listen.c
> +++ gotd/listen.c
> @@ -70,6 +70,8 @@ static struct {
> int fd;
> struct gotd_imsgev iev;
> struct gotd_imsgev pause;
> + struct gotd_uid_connection_limit *connection_limits;
> + size_t nconnection_limits;
> } gotd_listen;
>
> static int inflight;
> @@ -178,6 +180,26 @@ static const struct got_error *
> return NULL;
> }
>
> +struct gotd_uid_connection_limit *
> +gotd_find_uid_connection_limit(struct gotd_uid_connection_limit *limits,
> + size_t nlimits, uid_t uid)
> +{
> + /* This array is always sorted to allow for binary search. */
> + int i, left = 0, right = nlimits - 1;
> +
> + while (left <= right) {
> + i = ((left + right) / 2);
> + if (limits[i].uid == uid)
> + return &limits[i];
> + if (limits[i].uid > uid)
> + left = i + 1;
> + else
> + right = i - 1;
> + }
not really an issue, but since we already have
uid_connection_limit_cmp I wonder if we could reuse it here with
bsearch(3).
> + return NULL;
> +}
> +
> static const struct got_error *
> disconnect(struct gotd_listen_client *client)
> {
> @@ -303,7 +325,16 @@ gotd_accept(int fd, short event, void *arg)
> counter->nconnections = 1;
> add_uid_connection_counter(counter);
> } else {
> - if (counter->nconnections >= GOTD_MAX_CONN_PER_UID) {
> + int max_connections = GOTD_MAX_CONN_PER_UID;
> + struct gotd_uid_connection_limit *limit;
> +
> + limit = gotd_find_uid_connection_limit(
> + gotd_listen.connection_limits,
> + gotd_listen.nconnection_limits, euid);
> + if (limit)
> + max_connections = limit->max_connections;
> +
> + if (counter->nconnections >= max_connections) {
> log_warnx("maximum connections exceeded for uid %d",
> euid);
> goto err;
> @@ -426,7 +457,9 @@ listen_main(const char *title, int gotd_socket)
> }
>
> void
> -listen_main(const char *title, int gotd_socket)
> +listen_main(const char *title, int gotd_socket,
> + struct gotd_uid_connection_limit *connection_limits,
> + size_t nconnection_limits)
> {
> struct gotd_imsgev iev;
> struct event evsigint, evsigterm, evsighup, evsigusr1;
> @@ -437,6 +470,8 @@ listen_main(const char *title, int gotd_socket)
> gotd_listen.title = title;
> gotd_listen.pid = getpid();
> gotd_listen.fd = gotd_socket;
> + gotd_listen.connection_limits = connection_limits;
> + gotd_listen.nconnection_limits = nconnection_limits;
>
> signal_set(&evsigint, SIGINT, listen_sighdlr, NULL);
> signal_set(&evsigterm, SIGTERM, listen_sighdlr, NULL);
> @@ -473,6 +508,7 @@ listen_shutdown(void)
> {
> log_debug("%s: shutting down", gotd_listen.title);
>
> + free(gotd_listen.connection_limits);
> if (gotd_listen.fd != -1)
> close(gotd_listen.fd);
>
> blob - 5ecdb0084e77470530899068dc16cb0f553ead7b
> blob + e7a67532164502eb5ea8893b35007e6edf63ee36
> --- gotd/listen.h
> +++ gotd/listen.h
> @@ -14,4 +14,9 @@ void listen_main(const char *title, int gotd_socket);
> * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> */
>
> -void listen_main(const char *title, int gotd_socket);
> +struct gotd_uid_connection_limit *gotd_find_uid_connection_limit(
> + struct gotd_uid_connection_limit *limits, size_t nlimits, uid_t uid);
> +
> +void listen_main(const char *title, int gotd_socket,
> + struct gotd_uid_connection_limit *connection_limits,
> + size_t nconnection_limits);
> blob - eb5822c199bae7d3a0ab0b75eefe7854b6409e3c
> blob + 9acd293aaafa5ef3d24f7c8bf9a7caf2a5ec0cc5
> --- gotd/parse.y
> +++ gotd/parse.y
> @@ -46,6 +46,8 @@
>
> #include "log.h"
> #include "gotd.h"
> +#include "auth.h"
> +#include "listen.h"
>
> TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
> static struct file {
> @@ -94,6 +96,7 @@ typedef struct {
> union {
> long long number;
> char *string;
> + struct timeval tv;
> } v;
> int lineno;
> } YYSTYPE;
> @@ -101,11 +104,12 @@ typedef struct {
> %}
>
> %token PATH ERROR ON UNIX_SOCKET UNIX_GROUP USER REPOSITORY PERMIT DENY
> -%token RO RW
> +%token RO RW CONNECTION MAXIMUM AUTHENTICATION REQUEST TIMEOUT
>
> %token <v.string> STRING
> %token <v.number> NUMBER
> %type <v.number> boolean
> +%type <v.tv> timeout
>
> %%
>
> @@ -135,6 +139,17 @@ main : UNIX_SOCKET STRING {
> | NUMBER { $$ = $1; }
> ;
>
> +timeout : NUMBER
> + {
style nit: I'd keep { in the same line as all the other rules do.
> + if ($1 < 0) {
> + yyerror("invalid timeout: %lld", $1);
> + YYERROR;
> + }
> + $$.tv_sec = $1;
> + $$.tv_usec = 0;
> + }
> + ;
> +
> main : UNIX_SOCKET STRING {
> if (gotd_proc_id == PROC_LISTEN) {
> if (strlcpy(gotd->unix_socket_path, $2,
> @@ -169,8 +184,34 @@ main : UNIX_SOCKET STRING {
> }
> free($2);
> }
> + | connection
> ;
>
> +connection : CONNECTION '{' optnl conflags_l '}'
> + | CONNECTION conflags
> +
> +conflags_l : conflags optnl conflags_l
> + | conflags optnl
> + ;
> +
> +conflags : REQUEST TIMEOUT timeout {
> + memcpy(&gotd->request_timeout, &$3,
> + sizeof(struct timeval));
nit: I'd use sizeof(gotd->request_timeout). I don't think we'll ever
change the type, but if we do it'd be less churn.
> + }
> + | AUTHENTICATION TIMEOUT timeout {
> + memcpy(&gotd->auth_timeout, &$3,
> + sizeof(struct timeval));
ditto.
> + }
> + | USER STRING MAXIMUM NUMBER {
> + if (gotd_proc_id == PROC_LISTEN &&
> + conf_new_user_max_connections($2, $4) == -1) {
> + free($2);
> + YYERROR;
> + }
> + free($2);
> + }
> + ;
> +
> repository : REPOSITORY STRING {
> struct gotd_repo *repo;
>
> @@ -276,13 +317,18 @@ lookup(char *s)
> {
> /* This has to be sorted always. */
> static const struct keywords keywords[] = {
> + { "authentication", AUTHENTICATION },
> + { "connection", CONNECTION },
> { "deny", DENY },
> + { "max", MAXIMUM },
> { "on", ON },
> { "path", PATH },
> { "permit", PERMIT },
> { "repository", REPOSITORY },
> + { "request", REQUEST },
> { "ro", RO },
> { "rw", RW },
> + { "timeout", TIMEOUT },
> { "unix_group", UNIX_GROUP },
> { "unix_socket", UNIX_SOCKET },
> { "user", USER },
> @@ -637,6 +683,11 @@ parse_config(const char *filename, enum gotd_procid pr
> return -1;
> }
>
> + gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
> + gotd->request_timeout.tv_usec = 0;
> + gotd->auth_timeout.tv_sec = GOTD_DEFAULT_AUTH_TIMEOUT;
> + gotd->auth_timeout.tv_usec = 0;
> +
> file = newfile(filename, 0);
> if (file == NULL) {
> /* just return, as we don't require a conf file */
> @@ -674,6 +725,67 @@ static struct gotd_repo *
> return (0);
> }
>
> +static int
> +uid_connection_limit_cmp(const void *pa, const void *pb)
> +{
> + const struct gotd_uid_connection_limit *a, *b;
> +
> + a = (const struct gotd_uid_connection_limit *)pa;
> + b = (const struct gotd_uid_connection_limit *)pb;
not an issue, but the cast is not needed. could just
+ const struct gotd_uid_connection_limit *a = pa, *b = pb;
> + if (a->uid < b->uid)
> + return -1;
> + else if (a->uid > b->uid);
> + return 1;
> +
> + return 0;
> +}
> +
> +static int
> +conf_new_user_max_connections(const char *user, int maximum)
> +{
> + uid_t uid;
> + struct gotd_uid_connection_limit *limit;
> + size_t nlimits;
> +
> + if (maximum < 1) {
> + yyerror("max connections cannot be smaller 1");
> + return -1;
> + }
> + if (maximum > GOTD_MAXCLIENTS) {
> + yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
> + return -1;
> + }
> +
> + if (gotd_auth_parseuid(user, &uid) == -1) {
> + yyerror("%s: no such user", user);
> + return -1;
> + }
> +
> + limit = gotd_find_uid_connection_limit(gotd->connection_limits,
> + gotd->nconnection_limits, uid);
> + if (limit) {
> + limit->max_connections = maximum;
> + return 0;
> + }
> +
> + limit = gotd->connection_limits;
> + nlimits = gotd->nconnection_limits + 1;
> + limit = reallocarray(limit, nlimits, sizeof(*limit));
> + if (limit == NULL)
> + fatal("reallocarray");
> +
> + limit[nlimits - 1].uid = uid;
> + limit[nlimits - 1].max_connections = maximum;
> +
> + gotd->connection_limits = limit;
> + gotd->nconnection_limits = nlimits;
> + qsort(gotd->connection_limits, gotd->nconnection_limits,
> + sizeof(gotd->connection_limits[0]), uid_connection_limit_cmp);
> +
> + return 0;
> +}
> +
> static struct gotd_repo *
> conf_new_repo(const char *name)
> {
add gotd.conf connection options