This one is annoying. Again poll handling is throwing me a curve ball.
On MacOS X POLLHUP is signaled super early and many downloads fail.
The issue here is that tls_read() and tls_write() are not like read() and
write() from a poll perspective. While parsing the HTTP header for example
one should call tls_read() until a TLS_WANT_* return happens. Only then a
pass via poll() should happen.
In the current code there are many cases where WANT_POLLIN is returned
where actually another tls_read() should happen. It is similar but not as
bad for tls_write().
This diff tries to cleanup the mess a bit. In http_read() WANT_POLLIN
should only be triggered by tls_read(). In most cases we try to push
forward through multiple states as long as possible. I solved this with
two goto labels in http_read().
Because of this some of the http_nextstep() handling was pulled into
http_read() and e.g. data_write() is now also directly calling into
http_read() instead of passing via poll().
I tried this on OpenBSD, Linux and MacOS X and this version seems to work
on all three systems.
Please test since I may have missed or introduced another poll related
issue.
--
:wq Claudio
Index: http.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/http.c,v
retrieving revision 1.28
diff -u -p -r1.28 http.c
--- http.c 13 Apr 2021 13:54:15 -0000 1.28
+++ http.c 15 Apr 2021 13:24:57 -0000
@@ -123,6 +123,8 @@ struct tls_config *tls_config;
uint8_t *tls_ca_mem;
size_t tls_ca_size;
+static int http_write(struct http_connection *);
+
/*
* Return a string that can be used in error message to identify the
* connection.
@@ -699,7 +701,7 @@ http_redirect(struct http_connection *co
if (http_resolv(conn, host, port) == -1)
return -1;
- return http_connect(conn);
+ return -2;
}
static int
@@ -846,7 +848,9 @@ http_read(struct http_connection *conn)
{
ssize_t s;
char *buf;
+ int done;
+read_more:
s = tls_read(conn->tls, conn->buf + conn->bufpos,
conn->bufsz - conn->bufpos);
if (s == -1) {
@@ -867,38 +871,63 @@ http_read(struct http_connection *conn)
conn->bufpos += s;
+again:
switch (conn->state) {
case STATE_RESPONSE_STATUS:
buf = http_get_line(conn);
if (buf == NULL)
- return WANT_POLLIN;
+ goto read_more;
if (http_parse_status(conn, buf) == -1) {
free(buf);
return -1;
}
free(buf);
conn->state = STATE_RESPONSE_HEADER;
- /* FALLTHROUGH */
+ goto again;
case STATE_RESPONSE_HEADER:
- while ((buf = http_get_line(conn)) != NULL) {
+ done = 0;
+ while (!done) {
int rv;
+ buf = http_get_line(conn);
+ if (buf == NULL)
+ goto read_more;
+
rv = http_parse_header(conn, buf);
free(buf);
- if (rv != 1)
- return rv;
+ if (rv == -1)
+ return -1;
+ if (rv == -2) /* redirect */
+ return 0;
+ if (rv == 0)
+ done = 1;
}
- return WANT_POLLIN;
+
+ /* Check status header and decide what to do next */
+ if (conn->status == 200) {
+ if (conn->chunked)
+ conn->state = STATE_RESPONSE_CHUNKED;
+ else
+ conn->state = STATE_RESPONSE_DATA;
+ goto again;
+ } else if (conn->status == 304) {
+ http_done(conn->id, HTTP_NOT_MOD, conn->last_modified);
+ } else {
+ http_done(conn->id, HTTP_FAILED, conn->last_modified);
+ }
+
+ conn->state = STATE_DONE;
+ return 0;
case STATE_RESPONSE_DATA:
if (conn->bufpos == conn->bufsz ||
conn->iosz <= (off_t)conn->bufpos)
return 0;
- return WANT_POLLIN;
+ goto read_more;
case STATE_RESPONSE_CHUNKED:
while (conn->iosz == 0) {
buf = http_get_line(conn);
if (buf == NULL)
- return WANT_POLLIN;
+ goto read_more;
switch (http_parse_chunked(conn, buf)) {
case -1:
free(buf);
@@ -913,7 +942,7 @@ http_read(struct http_connection *conn)
if (conn->bufpos == conn->bufsz ||
conn->iosz <= (off_t)conn->bufpos)
return 0;
- return WANT_POLLIN;
+ goto read_more;
default:
errx(1, "unexpected http state");
}
@@ -939,6 +968,7 @@ http_write(struct http_connection *conn)
conn->bufpos += s;
if (conn->bufpos == conn->bufsz)
return 0;
+
return WANT_POLLOUT;
}
@@ -987,8 +1017,13 @@ data_write(struct http_connection *conn)
}
/* all data written, switch back to read */
- if (conn->bufpos == 0 || conn->iosz == 0)
- return 0;
+ if (conn->bufpos == 0 || conn->iosz == 0) {
+ if (conn->chunked)
+ conn->state = STATE_RESPONSE_CHUNKED;
+ else
+ conn->state = STATE_RESPONSE_DATA;
+ return http_read(conn);
+ }
/* still more data to write in buffer */
return WANT_POLLOUT;
@@ -1003,25 +1038,9 @@ data_write(struct http_connection *conn)
static int
http_handle(struct http_connection *conn, int events)
{
- /* special case for INIT, there is no event to start a request */
- 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 http_connect(conn);
case STATE_CONNECT:
if (http_finish_connect(conn) == -1)
/* something went wrong, try other host */
@@ -1037,10 +1056,13 @@ http_handle(struct http_connection *conn
case STATE_RESPONSE_CHUNKED:
return http_read(conn);
case STATE_WRITE_DATA:
+ if (events & POLLHUP) {
+ warnx("%s: output closed", http_info(conn->url));
+ return -1;
+ }
return data_write(conn);
case STATE_DONE:
return http_close(conn);
- case STATE_INIT:
case STATE_FREE:
errx(1, "bad http state");
}
@@ -1058,7 +1080,7 @@ http_nextstep(struct http_connection *co
switch (conn->state) {
case STATE_INIT:
- errx(1, "bad http state");
+ return http_connect(conn);
case STATE_CONNECT:
conn->state = STATE_TLSCONNECT;
r = http_tls_connect(conn);
@@ -1076,38 +1098,16 @@ http_nextstep(struct http_connection *co
err(1, NULL);
conn->bufpos = 0;
conn->bufsz = HTTP_BUF_SIZE;
- return WANT_POLLIN;
- case STATE_RESPONSE_STATUS:
- conn->state = STATE_RESPONSE_HEADER;
- return WANT_POLLIN;
- case STATE_RESPONSE_HEADER:
- if (conn->status == 200) {
- conn->state = STATE_RESPONSE_DATA;
- } else {
- if (conn->status == 304)
- http_done(conn->id, HTTP_NOT_MOD,
- conn->last_modified);
- else
- http_done(conn->id, HTTP_FAILED,
- conn->last_modified);
- conn->state = STATE_DONE;
- return http_close(conn);
- }
- return WANT_POLLIN;
+ return http_read(conn);
case STATE_RESPONSE_DATA:
- conn->state = STATE_WRITE_DATA;
- return WANT_POLLOUT;
case STATE_RESPONSE_CHUNKED:
conn->state = STATE_WRITE_DATA;
return WANT_POLLOUT;
- case STATE_WRITE_DATA:
- if (conn->chunked)
- conn->state = STATE_RESPONSE_CHUNKED;
- else
- conn->state = STATE_RESPONSE_DATA;
- return WANT_POLLIN;
case STATE_DONE:
return http_close(conn);
+ case STATE_RESPONSE_STATUS:
+ case STATE_RESPONSE_HEADER:
+ case STATE_WRITE_DATA:
case STATE_FREE:
errx(1, "bad http state");
}