Download raw body.
add gotd.conf connection options
On Mon, Jan 02, 2023 at 04:15:44PM +0100, Stefan Sperling wrote:
> Introduce connection options to gotd.conf.
>
> Allow administrators to tweak the default authentication and request
> timeouts if needed, and to tweak the limit of concurrent connections
> for specific user accounts.
>
> ok?
Ooops, the previous diff was not actually working because I forgot
to copy connection_limits to the gotd_listen struct in listen_main().
Fixed version.
diff 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3 a9284cf9de090824bf09849e4b94ab5ba3d590de
commit - 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3
commit + a9284cf9de090824bf09849e4b94ab5ba3d590de
blob - fd7f8c11aa319b18aa9f13289797a9809af40003
blob + 01a705f61574e2653156dbe6ccb60035616515ac
--- gotd/auth.c
+++ gotd/auth.c
@@ -72,8 +72,8 @@ static int
}
}
-static int
-parseuid(const char *s, uid_t *uid)
+int
+gotd_auth_parseuid(const char *s, uid_t *uid)
{
struct passwd *pw;
const char *errstr;
@@ -95,7 +95,7 @@ uidcheck(const char *s, uid_t desired)
{
uid_t uid;
- if (parseuid(s, &uid) != 0)
+ if (gotd_auth_parseuid(s, &uid) != 0)
return -1;
if (uid != desired)
return -1;
blob - fe78e2c9301e0c052e89de790159fb9afaeb5094
blob + 87cd12560d438c7e4b48d2f7a762ff678b51d92e
--- gotd/auth.h
+++ gotd/auth.h
@@ -14,6 +14,6 @@ void
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-void
-auth_main(const char *title, struct gotd_repolist *repos,
+int gotd_auth_parseuid(const char *, uid_t *);
+void auth_main(const char *title, struct gotd_repolist *repos,
const char *repo_path);
blob - 0f2528c97db20b779a500b205d84cfcdb238b7ad
blob + 59de8b44a2f5a60c66d8b93a79242dd674add08d
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -93,8 +93,6 @@ static struct timeval timeout = { 3600, 0 };
static struct gotd_clients gotd_clients[GOTD_CLIENT_TABLE_SIZE];
static SIPHASH_KEY clients_hash_key;
volatile int client_cnt;
-static struct timeval timeout = { 3600, 0 };
-static struct timeval auth_timeout = { 5, 0 };
static struct gotd gotd;
void gotd_sighdlr(int sig, short event, void *arg);
@@ -1202,9 +1200,9 @@ gotd_request(int fd, short events, void *arg)
} else {
gotd_imsg_event_add(&client->iev);
if (client->state == GOTD_STATE_EXPECT_LIST_REFS)
- evtimer_add(&client->tmo, &auth_timeout);
+ evtimer_add(&client->tmo, &gotd.auth_timeout);
else
- evtimer_add(&client->tmo, &timeout);
+ evtimer_add(&client->tmo, &gotd.request_timeout);
}
}
@@ -2586,7 +2584,8 @@ main(int argc, char **argv)
if (pledge("stdio sendfd unix", NULL) == -1)
err(1, "pledge");
#endif
- listen_main(title, fd);
+ listen_main(title, fd, gotd.connection_limits,
+ gotd.nconnection_limits);
/* NOTREACHED */
break;
case PROC_AUTH:
blob - 612eac16fcff173392e5868ae2bfa04f9cee4b0f
blob + 6aae892e95b0d5fb567d7a86854a30b77413a444
--- 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 ...
+.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.
+.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
+ 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;
+ }
+
+ 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
+ {
+ 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));
+ }
+ | AUTHENTICATION TIMEOUT timeout {
+ memcpy(&gotd->auth_timeout, &$3,
+ sizeof(struct timeval));
+ }
+ | 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;
+
+ 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