diff --git a/mongoose.c b/mongoose.c index 8b948f92889fd1cc3c22598f4e45b47fa9619433..d58085369a52a1deb3fdea69318da974d2d2d52d 100644 --- a/mongoose.c +++ b/mongoose.c @@ -2488,11 +2488,13 @@ void mg_if_connect_cb(struct mg_connection *nc, int err) { * either failure (and dealloc the connection) * or success (and proceed with connect() */ -static void resolve_cb(struct mg_dns_message *msg, void *data) { +static void resolve_cb(struct mg_dns_message *msg, void *data, + enum mg_resolve_err e) { struct mg_connection *nc = (struct mg_connection *) data; int i; int failure = -1; + nc->flags &= ~MG_F_RESOLVING; if (msg != NULL) { /* * Take the first DNS A answer and run... @@ -2512,6 +2514,11 @@ static void resolve_cb(struct mg_dns_message *msg, void *data) { } } + if (e == MG_RESOLVE_TIMEOUT) { + double now = time(NULL); + mg_call(nc, NULL, MG_EV_TIMER, &now); + } + /* * If we get there was no MG_DNS_A_RECORD in the answer */ @@ -2557,12 +2564,18 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address, * DNS resolution is required for host. * mg_parse_address() fills port in nc->sa, which we pass to resolve_cb() */ - if (mg_resolve_async(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc) != 0) { + struct mg_connection *dns_conn = NULL; + struct mg_resolve_async_opts o; + memset(&o, 0, sizeof(o)); + o.dns_conn = &dns_conn; + if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc, + o) != 0) { MG_SET_PTRPTR(opts.error_string, "cannot schedule DNS lookup"); mg_destroy_conn(nc); return NULL; } - + nc->priv_2 = dns_conn; + nc->flags |= MG_F_RESOLVING; return nc; #else MG_SET_PTRPTR(opts.error_string, "Resolver is disabled"); @@ -2700,6 +2713,21 @@ void mg_forward(struct mg_connection *from, struct mg_connection *to) { mg_send(to, from->recv_mbuf.buf, from->recv_mbuf.len); mbuf_remove(&from->recv_mbuf, from->recv_mbuf.len); } + +double mg_set_timer(struct mg_connection *c, double timestamp) { + double result = c->ev_timer_time; + c->ev_timer_time = timestamp; + /* + * If this connection is resolving, it's not in the list of active + * connections, so not processed yet. It has a DNS resolver connection + * linked to it. Set up a timer for the DNS connection. + */ + DBG(("%p %p %d", c, c->priv_2, c->flags & MG_F_RESOLVING)); + if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) { + ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp; + } + return result; +} #ifdef NS_MODULE_LINES #line 1 "src/net_if_socket.c" /**/ @@ -7835,6 +7863,7 @@ struct mg_resolve_async_request { void *data; time_t timeout; int max_retries; + enum mg_resolve_err err; /* state */ time_t last_time; @@ -7964,6 +7993,7 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) { case MG_EV_CONNECT: case MG_EV_POLL: if (req->retries > req->max_retries) { + req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT; nc->flags |= MG_F_CLOSE_IMMEDIATELY; break; } @@ -7977,9 +8007,11 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) { msg = (struct mg_dns_message *) MG_MALLOC(sizeof(*msg)); if (mg_parse_dns(nc->recv_mbuf.buf, *(int *) data, msg) == 0 && msg->num_answers > 0) { - req->callback(msg, req->data); + req->callback(msg, req->data, MG_RESOLVE_OK); nc->user_data = NULL; MG_FREE(req); + } else { + req->err = MG_RESOLVE_NO_ANSWERS; } MG_FREE(msg); nc->flags |= MG_F_CLOSE_IMMEDIATELY; @@ -7992,10 +8024,14 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) { nc->flags &= ~MG_F_CLOSE_IMMEDIATELY; mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len); break; + case MG_EV_TIMER: + req->err = MG_RESOLVE_TIMEOUT; + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + break; case MG_EV_CLOSE: /* If we got here with request still not done, fire an error callback. */ if (req != NULL) { - req->callback(NULL, req->data); + req->callback(NULL, req->data, req->err); nc->user_data = NULL; MG_FREE(req); } @@ -8017,7 +8053,7 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query, struct mg_connection *dns_nc; const char *nameserver = opts.nameserver_url; - DBG(("%s %d", name, query)); + DBG(("%s %d %p", name, query, opts.dns_conn)); /* resolve with DNS */ req = (struct mg_resolve_async_request *) MG_CALLOC(1, sizeof(*req)); @@ -8050,6 +8086,9 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query, return -1; } dns_nc->user_data = req; + if (opts.dns_conn != NULL) { + *opts.dns_conn = dns_nc; + } return 0; } diff --git a/mongoose.h b/mongoose.h index 14fae65e52968496243c0d81b28543483fd692a8..45809380cac73095581ff3cb0f82a296c325fa9b 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1063,6 +1063,35 @@ enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7, const char *init_js_file_name); #endif +/* + * Schedule MG_EV_TIMER event to be delivered at `timestamp` time. + * `timestamp` is a UNIX time (a number of seconds since Epoch). It is + * `double` instead of `time_t` to allow for sub-second precision. + * Return the old timer value. + * + * Example: set connect timeout to 1.5 seconds: + * + * ``` + * c = mg_connect(&mgr, "cesanta.com", ev_handler); + * mg_set_timer(c, time(NULL) + 1.5); + * ... + * + * void ev_handler(struct mg_connection *c, int ev, void *ev_data) { + * switch (ev) { + * case MG_EV_CONNECT: + * mg_set_timer(c, 0); // Clear connect timer + * break; + * case MG_EV_TIMER: + * log("Connect timeout"); + * c->flags |= MG_F_CLOSE_IMMEDIATELY; + * break; +``` + * + * NOTE: sub-second precision is not implemented yet, current granularity + * is 1 second. + */ +double mg_set_timer(struct mg_connection *c, double timestamp); + #ifdef __cplusplus } #endif /* __cplusplus */ @@ -2556,7 +2585,15 @@ void mg_dns_send_reply(struct mg_connection *, struct mg_dns_reply *); extern "C" { #endif /* __cplusplus */ -typedef void (*mg_resolve_callback_t)(struct mg_dns_message *, void *); +enum mg_resolve_err { + MG_RESOLVE_OK = 0, + MG_RESOLVE_NO_ANSWERS = 1, + MG_RESOLVE_EXCEEDED_RETRY_COUNT = 2, + MG_RESOLVE_TIMEOUT = 3 +}; + +typedef void (*mg_resolve_callback_t)(struct mg_dns_message *dns_message, + void *user_data, enum mg_resolve_err); /* Options for `mg_resolve_async_opt`. */ struct mg_resolve_async_opts { @@ -2565,6 +2602,7 @@ struct mg_resolve_async_opts { int timeout; /* in seconds; defaults to 5 if zero */ int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */ int only_literal; /* only resolves literal addrs; sync cb invocation */ + struct mg_connection **dns_conn; /* return DNS connection */ }; /* See `mg_resolve_async_opt()` */