diff options
Diffstat (limited to 'source4/lib/socket/connect_multi.c')
-rw-r--r-- | source4/lib/socket/connect_multi.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/source4/lib/socket/connect_multi.c b/source4/lib/socket/connect_multi.c new file mode 100644 index 0000000..b29fffb --- /dev/null +++ b/source4/lib/socket/connect_multi.c @@ -0,0 +1,392 @@ +/* + Unix SMB/CIFS implementation. + + Fire connect requests to a host and a number of ports, with a timeout + between the connect request. Return if the first connect comes back + successfully or return the last error. + + Copyright (C) Volker Lendecke 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "libcli/composite/composite.h" +#include "libcli/resolve/resolve.h" + +#define MULTI_PORT_DELAY 2000 /* microseconds */ + +/* + overall state +*/ +struct connect_multi_state { + struct socket_address **server_address; + unsigned num_address, current_address, current_port; + int num_ports; + uint16_t *ports; + + struct socket_context *sock; + uint16_t result_port; + + int num_connects_sent, num_connects_recv; + + struct socket_connect_multi_ex *ex; +}; + +/* + state of an individual socket_connect_send() call +*/ +struct connect_one_state { + struct composite_context *result; + struct socket_context *sock; + struct socket_address *addr; +}; + +static void continue_resolve_name(struct composite_context *creq); +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p); +static void connect_multi_next_socket(struct composite_context *result); +static void continue_one(struct composite_context *creq); +static void continue_one_ex(struct tevent_req *subreq); + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_ex_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex) +{ + struct composite_context *result; + struct connect_multi_state *multi; + int i; + + struct nbt_name name; + struct composite_context *creq; + + result = talloc_zero(mem_ctx, struct composite_context); + if (result == NULL) return NULL; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->event_ctx = event_ctx; + + multi = talloc_zero(result, struct connect_multi_state); + if (composite_nomem(multi, result)) goto failed; + result->private_data = multi; + + multi->num_ports = num_server_ports; + multi->ports = talloc_array(multi, uint16_t, multi->num_ports); + if (composite_nomem(multi->ports, result)) goto failed; + + for (i=0; i<multi->num_ports; i++) { + multi->ports[i] = server_ports[i]; + } + + multi->ex = ex; + + /* + we don't want to do the name resolution separately + for each port, so start it now, then only start on + the real sockets once we have an IP + */ + make_nbt_name_server(&name, server_name); + + creq = resolve_name_all_send(resolve_ctx, multi, 0, multi->ports[0], &name, result->event_ctx); + if (composite_nomem(creq, result)) goto failed; + + composite_continue(result, creq, continue_resolve_name, result); + + return result; + + + failed: + composite_error(result, result->status); + return result; +} + +/* + start connecting to the next socket/port in the list +*/ +static void connect_multi_next_socket(struct composite_context *result) +{ + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct connect_one_state *state; + struct composite_context *creq; + int next = multi->num_connects_sent; + + if (next == multi->num_address * multi->num_ports) { + /* don't do anything, just wait for the existing ones to finish */ + return; + } + + if (multi->current_address == multi->num_address) { + multi->current_address = 0; + multi->current_port += 1; + } + multi->num_connects_sent += 1; + + if (multi->server_address == NULL || multi->server_address[multi->current_address] == NULL) { + composite_error(result, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + state = talloc(multi, struct connect_one_state); + if (composite_nomem(state, result)) return; + + state->result = result; + result->status = socket_create( + state, multi->server_address[multi->current_address]->family, + SOCKET_TYPE_STREAM, &state->sock, 0); + if (!composite_is_ok(result)) return; + + state->addr = socket_address_copy(state, multi->server_address[multi->current_address]); + if (composite_nomem(state->addr, result)) return; + + socket_address_set_port(state->addr, multi->ports[multi->current_port]); + + creq = socket_connect_send(state->sock, NULL, + state->addr, 0, + result->event_ctx); + if (composite_nomem(creq, result)) return; + talloc_steal(state, creq); + + multi->current_address++; + composite_continue(result, creq, continue_one, state); + + /* if there are more ports / addresses to go then setup a timer to fire when we have waited + for a couple of milli-seconds, when that goes off we try the next port regardless + of whether this port has completed */ + if (multi->num_ports * multi->num_address > multi->num_connects_sent) { + /* note that this timer is a child of the single + connect attempt state, so it will go away when this + request completes */ + tevent_add_timer(result->event_ctx, state, + timeval_current_ofs_usec(MULTI_PORT_DELAY), + connect_multi_timer, result); + } +} + +/* + a timer has gone off telling us that we should try the next port +*/ +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p) +{ + struct composite_context *result = talloc_get_type(p, struct composite_context); + connect_multi_next_socket(result); +} + + +/* + recv name resolution reply then send the next connect +*/ +static void continue_resolve_name(struct composite_context *creq) +{ + struct composite_context *result = talloc_get_type(creq->async.private_data, + struct composite_context); + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct socket_address **addr; + unsigned i; + + result->status = resolve_name_all_recv(creq, multi, &addr, NULL); + if (!composite_is_ok(result)) return; + + for(i=0; addr[i]; i++); + multi->num_address = i; + multi->server_address = talloc_steal(multi, addr); + + connect_multi_next_socket(result); +} + +/* + one of our socket_connect_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one(struct composite_context *creq) +{ + struct connect_one_state *state = talloc_get_type(creq->async.private_data, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + NTSTATUS status; + + status = socket_connect_recv(creq); + + if (multi->ex) { + struct tevent_req *subreq; + + subreq = multi->ex->establish_send(state, + result->event_ctx, + state->sock, + state->addr, + multi->ex->private_data); + if (composite_nomem(subreq, result)) return; + tevent_req_set_callback(subreq, continue_one_ex, state); + return; + } + + multi->num_connects_recv++; + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + one of our multi->ex->establish_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one_ex(struct tevent_req *subreq) +{ + struct connect_one_state *state = + tevent_req_callback_data(subreq, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = + talloc_get_type_abort(result->private_data, + struct connect_multi_state); + NTSTATUS status; + multi->num_connects_recv++; + + status = multi->ex->establish_recv(subreq); + TALLOC_FREE(subreq); + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_ex_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + NTSTATUS status = composite_wait(ctx); + if (NT_STATUS_IS_OK(status)) { + struct connect_multi_state *multi = + talloc_get_type(ctx->private_data, + struct connect_multi_state); + *sock = talloc_steal(mem_ctx, multi->sock); + *port = multi->result_port; + } + talloc_free(ctx); + return status; +} + +NTSTATUS socket_connect_multi_ex(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex, + struct socket_context **result, + uint16_t *result_port) +{ + struct composite_context *ctx = + socket_connect_multi_ex_send(mem_ctx, server_address, + num_server_ports, server_ports, + resolve_ctx, + event_ctx, + ex); + return socket_connect_multi_ex_recv(ctx, mem_ctx, result, result_port); +} + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx) +{ + return socket_connect_multi_ex_send(mem_ctx, + server_name, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL); /* ex */ +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + return socket_connect_multi_ex_recv(ctx, mem_ctx, sock, port); +} + +NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_context **result, + uint16_t *result_port) +{ + return socket_connect_multi_ex(mem_ctx, + server_address, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL, /* ex */ + result, + result_port); +} |