On Thu, Apr 08, 2021 at 04:47:15PM +0200, Claudio Jeker wrote:
> This diff is a first step in tightening the code in http.c
> It should cleanup the poll handling and make adds some code to ensure that
> only expected results are returned. The goal is that http_handle() only
> does IO processing and http_nextstep() is used for transitions into new
> states.
I like this direction.
> I did shuffle the code around a bit. So that similar functions are together.
> Also by shuffling the poll loop now connections are added after the all
> active connections have been processed.
This added quite a bit of noise to the diff (especially some parts
shuffling _write() functions are hard to read).
I'm ok with this, one small comment below.
>
> --
> :wq Claudio
>
> Index: http.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/http.c,v
> retrieving revision 1.21
> diff -u -p -r1.21 http.c
> --- http.c 7 Apr 2021 16:40:38 -0000 1.21
> +++ http.c 8 Apr 2021 14:17:01 -0000
> @@ -107,9 +107,7 @@ struct http_connection {
> size_t bufsz;
> size_t bufpos;
> size_t id;
> - size_t chunksz;
> - off_t filesize;
> - off_t bytes;
> + off_t iosz;
> int status;
> int redirect_loop;
> int fd;
> @@ -132,7 +130,7 @@ size_t tls_ca_size;
> static const char *
> http_info(const char *url)
> {
> - static char buf[64];
> + static char buf[80];
>
> if (strnvis(buf, url, sizeof buf, VIS_SAFE) >= (int)sizeof buf) {
> /* overflow, add indicator */
> @@ -312,24 +310,6 @@ http_free(struct http_connection *conn)
> }
>
> static int
> -http_close(struct http_connection *conn)
> -{
> - if (conn->tls != NULL) {
> - switch (tls_close(conn->tls)) {
> - case TLS_WANT_POLLIN:
> - return WANT_POLLIN;
> - case TLS_WANT_POLLOUT:
> - return WANT_POLLOUT;
> - case 0:
> - case -1:
> - break;
> - }
> - }
> -
> - return -1;
> -}
> -
> -static int
> http_parse_uri(char *uri, char **ohost, char **oport, char **opath)
> {
> char *host, *port = NULL, *path;
> @@ -426,50 +406,12 @@ http_new(size_t id, char *uri, char *mod
> }
>
> static int
> -http_redirect(struct http_connection *conn, char *uri)
> -{
> - char *host, *port, *path;
> -
> - logx("redirect to %s", http_info(uri));
> -
> - if (http_parse_uri(uri, &host, &port, &path) == -1) {
> - free(uri);
> - return -1;
> - }
> -
> - free(conn->url);
> - conn->url = uri;
> - free(conn->host);
> - conn->host = host;
> - free(conn->port);
> - conn->port = port;
> - conn->path = path;
> - /* keep modified_since since that is part of the request */
> - free(conn->last_modified);
> - conn->last_modified = NULL;
> - free(conn->buf);
> - conn->buf = NULL;
> - conn->bufpos = 0;
> - conn->bufsz = 0;
> - tls_close(conn->tls);
> - tls_free(conn->tls);
> - conn->tls = NULL;
> - close(conn->fd);
> - conn->state = STATE_INIT;
> -
> - /* TODO proxy support (overload of host and port) */
> -
> - if (http_resolv(conn, host, port) == -1)
> - return -1;
> -
> - return 0;
> -}
> -
> -static int
> http_connect(struct http_connection *conn)
> {
> const char *cause = NULL;
>
> + conn->state = STATE_CONNECT;
> +
> if (conn->fd != -1) {
> close(conn->fd);
> conn->fd = -1;
> @@ -531,6 +473,7 @@ http_connect(struct http_connection *con
>
> freeaddrinfo(conn->res0);
> conn->res0 = NULL;
> + conn->res = NULL;
A few lines before this, there's another freeaddrinfo() where you don't
NULL out conn->res. I don't think it matters since you'll promptly end
in the case -1: of http_do(), but I would still do it for consistency.
>
> #if 0
> /* TODO proxy connect */
> @@ -560,6 +503,7 @@ http_finish_connect(struct http_connecti
>
> freeaddrinfo(conn->res0);
> conn->res0 = NULL;
> + conn->res = NULL;
>
> #if 0
> /* TODO proxy connect */
> @@ -666,29 +610,6 @@ http_request(struct http_connection *con
> }
>
> static int
> -http_write(struct http_connection *conn)
> -{
> - ssize_t s;
> -
> - s = tls_write(conn->tls, conn->buf + conn->bufpos,
> - conn->bufsz - conn->bufpos);
> - if (s == -1) {
> - warnx("%s: TLS write: %s", http_info(conn->url),
> - tls_error(conn->tls));
> - return -1;
> - } else if (s == TLS_WANT_POLLIN) {
> - return WANT_POLLIN;
> - } else if (s == TLS_WANT_POLLOUT) {
> - return WANT_POLLOUT;
> - }
> -
> - conn->bufpos += s;
> - if (conn->bufpos == conn->bufsz)
> - return 0;
> - return WANT_POLLOUT;
> -}
> -
> -static int
> http_parse_status(struct http_connection *conn, char *buf)
> {
> const char *errstr;
> @@ -746,6 +667,46 @@ http_isredirect(struct http_connection *
> }
>
> static int
> +http_redirect(struct http_connection *conn, char *uri)
> +{
> + char *host, *port, *path;
> +
> + logx("redirect to %s", http_info(uri));
> +
> + if (http_parse_uri(uri, &host, &port, &path) == -1) {
> + free(uri);
> + return -1;
> + }
> +
> + free(conn->url);
> + conn->url = uri;
> + free(conn->host);
> + conn->host = host;
> + free(conn->port);
> + conn->port = port;
> + conn->path = path;
> + /* keep modified_since since that is part of the request */
> + free(conn->last_modified);
> + conn->last_modified = NULL;
> + free(conn->buf);
> + conn->buf = NULL;
> + conn->bufpos = 0;
> + conn->bufsz = 0;
> + tls_close(conn->tls);
> + tls_free(conn->tls);
> + conn->tls = NULL;
> + close(conn->fd);
> + conn->state = STATE_INIT;
> +
> + /* TODO proxy support (overload of host and port) */
> +
> + if (http_resolv(conn, host, port) == -1)
> + return -1;
> +
> + return http_connect(conn);
> +}
> +
> +static int
> http_parse_header(struct http_connection *conn, char *buf)
> {
> #define CONTENTLEN "Content-Length: "
> @@ -765,10 +726,10 @@ http_parse_header(struct http_connection
> cp += sizeof(CONTENTLEN) - 1;
> if ((s = strcspn(cp, " \t")) != 0)
> *(cp+s) = 0;
> - conn->filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
> + conn->iosz = strtonum(cp, 0, LLONG_MAX, &errstr);
> if (errstr != NULL) {
> - warnx("Improper response from %s",
> - http_info(conn->url));
> + warnx("Content-Length of %s is %s",
> + http_info(conn->url), errstr);
> return -1;
> }
> } else if (http_isredirect(conn) &&
> @@ -858,7 +819,7 @@ http_parse_chunked(struct http_connectio
> {
> char *header = buf;
> char *end;
> - int chunksize;
> + unsigned long chunksize;
>
> /* ignore empty lines, used between chunk and next header */
> if (*header == '\0')
> @@ -868,14 +829,14 @@ http_parse_chunked(struct http_connectio
> header[strcspn(header, ";\r\n")] = '\0';
> errno = 0;
> chunksize = strtoul(header, &end, 16);
> - if (errno != 0 || header[0] == '\0' || *end != '\0' ||
> - chunksize > INT_MAX) {
> + if (header[0] == '\0' || *end != '\0' || (errno == ERANGE &&
> + chunksize == ULONG_MAX) || chunksize > INT_MAX) {
> warnx("%s: Invalid chunk size", http_info(conn->url));
> return -1;
> }
> - conn->chunksz = chunksize;
> + conn->iosz = chunksize;
>
> - if (conn->chunksz == 0) {
> + if (conn->iosz == 0) {
> http_done(conn, HTTP_OK);
> return 0;
> }
> @@ -933,11 +894,11 @@ http_read(struct http_connection *conn)
> return WANT_POLLIN;
> case STATE_RESPONSE_DATA:
> if (conn->bufpos == conn->bufsz ||
> - conn->filesize - conn->bytes <= (off_t)conn->bufpos)
> + conn->iosz <= (off_t)conn->bufpos)
> return 0;
> return WANT_POLLIN;
> case STATE_RESPONSE_CHUNKED:
> - while (conn->chunksz == 0) {
> + while (conn->iosz == 0) {
> buf = http_get_line(conn);
> if (buf == NULL)
> return WANT_POLLIN;
> @@ -953,7 +914,7 @@ http_read(struct http_connection *conn)
> }
>
> if (conn->bufpos == conn->bufsz ||
> - conn->chunksz <= conn->bufpos)
> + conn->iosz <= (off_t)conn->bufpos)
> return 0;
> return WANT_POLLIN;
> default:
> @@ -962,67 +923,106 @@ http_read(struct http_connection *conn)
> }
>
> static int
> -data_write(struct http_connection *conn)
> +http_write(struct http_connection *conn)
> {
> ssize_t s;
> - size_t bsz = conn->bufpos;
> -
> - if (conn->filesize - conn->bytes < (off_t)bsz)
> - bsz = conn->filesize - conn->bytes;
>
> - s = write(conn->outfd, conn->buf, bsz);
> + s = tls_write(conn->tls, conn->buf + conn->bufpos,
> + conn->bufsz - conn->bufpos);
> if (s == -1) {
> - warn("%s: Data write", http_info(conn->url));
> + warnx("%s: TLS write: %s", http_info(conn->url),
> + tls_error(conn->tls));
> return -1;
> + } else if (s == TLS_WANT_POLLIN) {
> + return WANT_POLLIN;
> + } else if (s == TLS_WANT_POLLOUT) {
> + return WANT_POLLOUT;
> }
>
> - conn->bufpos -= s;
> - conn->bytes += s;
> - memmove(conn->buf, conn->buf + s, conn->bufpos);
> -
> - if (conn->bytes == conn->filesize) {
> - http_done(conn, HTTP_OK);
> + conn->bufpos += s;
> + if (conn->bufpos == conn->bufsz)
> return 0;
> - }
> + return WANT_POLLOUT;
> +}
>
> - if (conn->bufpos == 0)
> - return 0;
> +static int
> +http_close(struct http_connection *conn)
> +{
> + if (conn->tls != NULL) {
> + switch (tls_close(conn->tls)) {
> + case TLS_WANT_POLLIN:
> + return WANT_POLLIN;
> + case TLS_WANT_POLLOUT:
> + return WANT_POLLOUT;
> + case 0:
> + case -1:
> + break;
> + }
> + }
>
> - return WANT_POLLOUT;
> + return -1;
> }
>
> static int
> -chunk_write(struct http_connection *conn)
> +data_write(struct http_connection *conn)
> {
> ssize_t s;
> size_t bsz = conn->bufpos;
>
> - if (bsz > conn->chunksz)
> - bsz = conn->chunksz;
> + if (conn->iosz < (off_t)bsz)
> + bsz = conn->iosz;
>
> s = write(conn->outfd, conn->buf, bsz);
> if (s == -1) {
> - warn("%s: Chunk write", http_info(conn->url));
> + warn("%s: data write", http_info(conn->url));
> return -1;
> }
>
> conn->bufpos -= s;
> - conn->chunksz -= s;
> - conn->bytes += s;
> + conn->iosz -= s;
> memmove(conn->buf, conn->buf + s, conn->bufpos);
>
> - if (conn->bufpos == 0 || conn->chunksz == 0)
> + /* check if regular file transfer is finished */
> + if (conn->iosz == 0 && conn->chunked == 0) {
> + http_done(conn, HTTP_OK);
> + return 0;
> + }
> +
> + /* all data written, switch back to read */
> + if (conn->bufpos == 0 || conn->iosz == 0)
> return 0;
>
> + /* still more data to write in buffer */
> return WANT_POLLOUT;
> }
>
> +/*
> + * Do one IO call depending on the connection state.
> + * Return WANT_POLLIN or WANT_POLLOUT to poll for more data.
> + * If 0 is returned this stage is finished and the protocol should move
> + * to the next stage by calling http_nextstep(). On error return -1.
> + */
> static int
> -http_handle(struct http_connection *conn)
> +http_handle(struct http_connection *conn, int events)
> {
> + if (conn->state == STATE_INIT)
> + return http_connect(conn);
> +
> + if (events & POLLHUP) {
> + if (conn->state == STATE_WRITE_DATA)
> + warnx("%s: output pipe closed", http_info(conn->url));
> + else
> + warnx("%s: server closed connection",
> + http_info(conn->url));
> + return -1;
> + }
> +
> + if ((events & conn->events) == 0) {
> + warnx("%s: unexpected event", http_info(conn->url));
> + return -1;
> + }
> +
> switch (conn->state) {
> - case STATE_INIT:
> - return 0;
> case STATE_CONNECT:
> if (http_finish_connect(conn) == -1)
> /* something went wrong, try other host */
> @@ -1038,28 +1038,34 @@ http_handle(struct http_connection *conn
> case STATE_RESPONSE_CHUNKED:
> return http_read(conn);
> case STATE_WRITE_DATA:
> - if (conn->chunked)
> - return chunk_write(conn);
> - else
> - return data_write(conn);
> + return data_write(conn);
> case STATE_DONE:
> return http_close(conn);
> + case STATE_INIT:
> case STATE_FREE:
> errx(1, "bad http state");
> }
> errx(1, "unknown http state");
> }
>
> +/*
> + * Move the state machine forward until IO needs to happen.
> + * Returns either WANT_POLLIN or WANT_POLLOUT or -1 on error.
> + */
> static int
> http_nextstep(struct http_connection *conn)
> {
> + int r;
> +
> switch (conn->state) {
> case STATE_INIT:
> - conn->state = STATE_CONNECT;
> - return http_connect(conn);
> + errx(1, "bad http state");
> case STATE_CONNECT:
> conn->state = STATE_TLSCONNECT;
> - return http_tls_connect(conn);
> + r = http_tls_connect(conn);
> + if (r != 0)
> + return r;
> + /* FALLTHROUGH */
> case STATE_TLSCONNECT:
> conn->state = STATE_REQUEST;
> return http_request(conn);
> @@ -1107,9 +1113,9 @@ http_nextstep(struct http_connection *co
> }
>
> static int
> -http_do(struct http_connection *conn)
> +http_do(struct http_connection *conn, int events)
> {
> - switch (http_handle(conn)) {
> + switch (http_handle(conn, events)) {
> case -1:
> /* connection failure */
> if (conn->state != STATE_DONE)
> @@ -1129,6 +1135,9 @@ http_do(struct http_connection *conn)
> http_fail(conn->id);
> http_free(conn);
> return -1;
> + case 0:
> + errx(1, "%s: http_nextstep returned 0, state %d",
> + http_info(conn->url), conn->state);
> }
> break;
> case WANT_POLLIN:
> @@ -1219,6 +1228,22 @@ proc_http(char *bind_addr, int fd)
> err(1, "write");
> }
> }
> +
> + /* process active http requests */
> + for (i = 0; i < MAX_CONNECTIONS; i++) {
> + struct http_connection *conn = http_conns[i];
> +
> + if (conn == NULL)
> + continue;
> + /* event not ready */
> + if (pfds[i].revents == 0)
> + continue;
> +
> + if (http_do(conn, pfds[i].revents) == -1)
> + http_conns[i] = NULL;
> + }
> +
> + /* process new requests last */
> if (pfds[MAX_CONNECTIONS].revents & POLLIN) {
> struct http_connection *h;
> size_t id;
> @@ -1236,23 +1261,11 @@ proc_http(char *bind_addr, int fd)
> if (http_conns[i] != NULL)
> continue;
> http_conns[i] = h;
> - if (http_do(h) == -1)
> + if (http_do(h, 0) == -1)
> http_conns[i] = NULL;
> break;
> }
> }
> - }
> - for (i = 0; i < MAX_CONNECTIONS; i++) {
> - struct http_connection *conn = http_conns[i];
> -
> - if (conn == NULL)
> - continue;
> - /* event not ready */
> - if (!(pfds[i].revents & (conn->events | POLLHUP)))
> - continue;
> -
> - if (http_do(conn) == -1)
> - http_conns[i] = NULL;
> }
> }
>
>