From 0c7f0405e36c45c88139189dd4c720aa0c4903f5 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 1 Oct 2013 19:12:13 +0400 Subject: [PATCH 1/2] http: implement new evhttp_connection_get_addr() api. Basically tcp final handshake looks like this: (C - client, S - server) ACK[C] - FIN/ACK[S] - FIN/ACK[S] - ACK [C] However there are servers, that didn't close connection like this, while it is still _considered_ as valid, and using libevent http layer we can do requests to such servers. Modified handshake: (C - client, S - server) ACK[C] - RST/ACK[S] - RST/ACK[S] And in this case we can't extract IP address from socket, because it is already closed, and getpeername() will return: "transport endpoint is not connected". So we need to store address that we are connecting to, after we know it, and that is what this patch do. I have reproduced it, however it have some extra packages. (I will try to fix it) https://github.com/azat/nfq-examples/blob/master/nfqnl_rst_fin.c --- http-internal.h | 7 +++++++ http.c | 18 ++++++++++++++++++ include/event2/http.h | 9 +++++++++ 3 files changed, 34 insertions(+) diff --git a/http-internal.h b/http-internal.h index d25753bb..82dd402a 100644 --- a/http-internal.h +++ b/http-internal.h @@ -99,6 +99,13 @@ struct evhttp_connection { struct event_base *base; struct evdns_base *dns_base; + + /* Saved conn_addr, to extract IP address from it. + * + * Because some servers may reset/close connection without waiting clients, + * in that case we can't extract IP address even in close_cb. + * So we need to save it, just after we connected to remote server. */ + struct sockaddr_storage *conn_address; }; /* A callback for an http server */ diff --git a/http.c b/http.c index 5600e2fc..e8672b77 100644 --- a/http.c +++ b/http.c @@ -1168,6 +1168,9 @@ evhttp_connection_free(struct evhttp_connection *evcon) if (evcon->address != NULL) mm_free(evcon->address); + if (evcon->conn_address != NULL) + mm_free(evcon->conn_address); + mm_free(evcon); } @@ -1404,6 +1407,7 @@ evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg) struct evhttp_connection *evcon = arg; int error; ev_socklen_t errsz = sizeof(error); + socklen_t conn_address_len = sizeof(*evcon->conn_address); if (evcon->fd == -1) evcon->fd = bufferevent_getfd(bufev); @@ -1454,6 +1458,14 @@ evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg) evcon->retry_cnt = 0; evcon->state = EVCON_IDLE; + if (!evcon->conn_address) { + evcon->conn_address = mm_malloc(sizeof(*evcon->conn_address)); + } + if (getpeername(evcon->fd, (struct sockaddr *)evcon->conn_address, &conn_address_len)) { + mm_free(evcon->conn_address); + evcon->conn_address = NULL; + } + /* reset the bufferevent cbs */ bufferevent_setcb(evcon->bufev, evhttp_read_cb, @@ -2348,6 +2360,12 @@ evhttp_connection_get_peer(struct evhttp_connection *evcon, *port = evcon->port; } +const struct sockaddr* +evhttp_connection_get_addr(struct evhttp_connection *evcon) +{ + return (struct sockaddr *)evcon->conn_address; +} + int evhttp_connection_connect_(struct evhttp_connection *evcon) { diff --git a/include/event2/http.h b/include/event2/http.h index cf44941d..956d9d6c 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -614,6 +614,15 @@ void evhttp_connection_set_closecb(struct evhttp_connection *evcon, void evhttp_connection_get_peer(struct evhttp_connection *evcon, char **address, ev_uint16_t *port); +/** Get the remote address associated with this connection. + * extracted from getpeername(). + * + * @return NULL if getpeername() return non success, + * or connection is not connected, + * otherwise it return pointer to struct sockaddr_storage */ +const struct sockaddr* +evhttp_connection_get_addr(struct evhttp_connection *evcon); + /** Make an HTTP request over the specified connection. From 4dd500cdf481b467be5bc12867cc1af15c48dea8 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 1 Oct 2013 19:54:09 +0400 Subject: [PATCH 2/2] regress_http: add tests for evhttp_connection_get_addr() --- test/regress_http.c | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/regress_http.c b/test/regress_http.c index 6389c4d7..faebabc4 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -3664,6 +3664,69 @@ http_ipv6_for_domain_test(void *arg) regress_clean_dnsserver(); } +static void +http_request_get_addr_on_close(struct evhttp_connection *evcon, void *arg) +{ + const struct sockaddr *storage; + char addrbuf[128]; + char local[] = "127.0.0.1:"; + + test_ok = 0; + tt_assert(evcon); + + storage = evhttp_connection_get_addr(evcon); + tt_assert(storage); + + evutil_format_sockaddr_port_((struct sockaddr *)storage, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, local, sizeof(local) - 1)); + + test_ok = 1; + return; + +end: + test_ok = 0; +} + +static void +http_get_addr_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + test_ok = 0; + exit_base = data->base; + + http = http_setup(&port, data->base, 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + evhttp_connection_set_closecb(evcon, http_request_get_addr_on_close, arg); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, (void *)BASIC_REQUEST_BODY); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + http_request_get_addr_on_close(evcon, NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + #define HTTP_LEGACY(name) \ { #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \ http_##name##_test } @@ -3713,6 +3776,7 @@ struct testcase_t http_testcases[] = { HTTP(data_length_constraints), HTTP(ipv6_for_domain), + HTTP(get_addr), END_OF_TESTCASES };