2f736a4b05027f256d508cf20d97ee13d2da3279
[sfrench/samba-autobuild/.git] / source4 / lib / socket / connect_multi.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Fire connect requests to a host and a number of ports, with a timeout
5    between the connect request. Return if the first connect comes back
6    successfully or return the last error.
7
8    Copyright (C) Volker Lendecke 2005
9    
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 3 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "includes.h"
25 #include "lib/socket/socket.h"
26 #include "lib/events/events.h"
27 #include "libcli/composite/composite.h"
28 #include "libcli/resolve/resolve.h"
29 #include "param/param.h"
30
31 #define MULTI_PORT_DELAY 2000 /* microseconds */
32
33 /*
34   overall state
35 */
36 struct connect_multi_state {
37         const char *server_address;
38         int num_ports;
39         uint16_t *ports;
40
41         struct resolve_context *resolve_ctx;
42
43         struct socket_context *sock;
44         uint16_t result_port;
45
46         int num_connects_sent, num_connects_recv;
47 };
48
49 /*
50   state of an individual socket_connect_send() call
51 */
52 struct connect_one_state {
53         struct composite_context *result;
54         struct socket_context *sock;
55         struct socket_address *addr;
56 };
57
58 static void continue_resolve_name(struct composite_context *creq);
59 static void connect_multi_timer(struct event_context *ev,
60                                     struct timed_event *te,
61                                     struct timeval tv, void *p);
62 static void connect_multi_next_socket(struct composite_context *result);
63 static void continue_one(struct composite_context *creq);
64
65 /*
66   setup an async socket_connect, with multiple ports
67 */
68 _PUBLIC_ struct composite_context *socket_connect_multi_send(
69                                                     TALLOC_CTX *mem_ctx,
70                                                     const char *server_address,
71                                                     int num_server_ports,
72                                                     uint16_t *server_ports,
73                                                     struct resolve_context *resolve_ctx,
74                                                     struct event_context *event_ctx)
75 {
76         struct composite_context *result;
77         struct connect_multi_state *multi;
78         int i;
79
80         result = talloc_zero(mem_ctx, struct composite_context);
81         if (result == NULL) return NULL;
82         result->state = COMPOSITE_STATE_IN_PROGRESS;
83         result->event_ctx = event_ctx;
84
85         multi = talloc_zero(result, struct connect_multi_state);
86         if (composite_nomem(multi, result)) goto failed;
87         result->private_data = multi;
88
89         multi->server_address = talloc_strdup(multi, server_address);
90         if (composite_nomem(multi->server_address, result)) goto failed;
91
92         multi->num_ports = num_server_ports;
93         multi->resolve_ctx = talloc_reference(multi, resolve_ctx);
94         multi->ports = talloc_array(multi, uint16_t, multi->num_ports);
95         if (composite_nomem(multi->ports, result)) goto failed;
96
97         for (i=0; i<multi->num_ports; i++) {
98                 multi->ports[i] = server_ports[i];
99         }
100
101         if (!is_ipaddress(server_address)) {
102                 /*  
103                     we don't want to do the name resolution separately
104                     for each port, so start it now, then only start on
105                     the real sockets once we have an IP
106                  */
107                 struct nbt_name name;
108                 struct composite_context *creq;
109                 make_nbt_name_client(&name, server_address);
110                 creq = resolve_name_send(resolve_ctx, &name, result->event_ctx);
111                 if (composite_nomem(creq, result)) goto failed;
112                 composite_continue(result, creq, continue_resolve_name, result);
113                 return result;
114         }
115
116         /* now we've setup the state we can process the first socket */
117         connect_multi_next_socket(result);
118
119         if (!NT_STATUS_IS_OK(result->status)) {
120                 goto failed;
121         }
122
123         return result;
124
125  failed:
126         composite_error(result, result->status);
127         return result;
128 }
129
130 /*
131   start connecting to the next socket/port in the list
132 */
133 static void connect_multi_next_socket(struct composite_context *result)
134 {
135         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
136                                                             struct connect_multi_state);
137         struct connect_one_state *state;
138         struct composite_context *creq;
139         int next = multi->num_connects_sent;
140
141         if (next == multi->num_ports) {
142                 /* don't do anything, just wait for the existing ones to finish */
143                 return;
144         }
145
146         multi->num_connects_sent += 1;
147
148         state = talloc(multi, struct connect_one_state);
149         if (composite_nomem(state, result)) return;
150
151         state->result = result;
152         result->status = socket_create("ipv4", SOCKET_TYPE_STREAM, &state->sock, 0);
153         if (!composite_is_ok(result)) return;
154
155         /* Form up the particular address we are interested in */
156         state->addr = socket_address_from_strings(state, state->sock->backend_name, 
157                                                   multi->server_address, multi->ports[next]);
158         if (composite_nomem(state->addr, result)) return;
159
160         talloc_steal(state, state->sock);
161
162         creq = socket_connect_send(state->sock, NULL, 
163                                    state->addr, 0, multi->resolve_ctx, 
164                                    result->event_ctx);
165         if (composite_nomem(creq, result)) return;
166         talloc_steal(state, creq);
167
168         composite_continue(result, creq, continue_one, state);
169
170         /* if there are more ports to go then setup a timer to fire when we have waited
171            for a couple of milli-seconds, when that goes off we try the next port regardless
172            of whether this port has completed */
173         if (multi->num_ports > multi->num_connects_sent) {
174                 /* note that this timer is a child of the single
175                    connect attempt state, so it will go away when this
176                    request completes */
177                 event_add_timed(result->event_ctx, state,
178                                 timeval_current_ofs(0, MULTI_PORT_DELAY),
179                                 connect_multi_timer, result);
180         }
181 }
182
183 /*
184   a timer has gone off telling us that we should try the next port
185 */
186 static void connect_multi_timer(struct event_context *ev,
187                                 struct timed_event *te,
188                                 struct timeval tv, void *p)
189 {
190         struct composite_context *result = talloc_get_type(p, struct composite_context);
191         connect_multi_next_socket(result);
192 }
193
194
195 /*
196   recv name resolution reply then send the next connect
197 */
198 static void continue_resolve_name(struct composite_context *creq)
199 {
200         struct composite_context *result = talloc_get_type(creq->async.private_data, 
201                                                            struct composite_context);
202         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
203                                                             struct connect_multi_state);
204         const char *addr;
205
206         result->status = resolve_name_recv(creq, multi, &addr);
207         if (!composite_is_ok(result)) return;
208
209         multi->server_address = addr;
210
211         connect_multi_next_socket(result);
212 }
213
214 /*
215   one of our socket_connect_send() calls hash finished. If it got a
216   connection or there are none left then we are done
217 */
218 static void continue_one(struct composite_context *creq)
219 {
220         struct connect_one_state *state = talloc_get_type(creq->async.private_data, 
221                                                           struct connect_one_state);
222         struct composite_context *result = state->result;
223         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
224                                                             struct connect_multi_state);
225         NTSTATUS status;
226         multi->num_connects_recv++;
227
228         status = socket_connect_recv(creq);
229
230         if (NT_STATUS_IS_OK(status)) {
231                 multi->sock = talloc_steal(multi, state->sock);
232                 multi->result_port = state->addr->port;
233         }
234
235         talloc_free(state);
236
237         if (NT_STATUS_IS_OK(status) || 
238             multi->num_connects_recv == multi->num_ports) {
239                 result->status = status;
240                 composite_done(result);
241                 return;
242         }
243
244         /* try the next port */
245         connect_multi_next_socket(result);
246 }
247
248 /*
249   async recv routine for socket_connect_multi()
250  */
251 _PUBLIC_ NTSTATUS socket_connect_multi_recv(struct composite_context *ctx,
252                                    TALLOC_CTX *mem_ctx,
253                                    struct socket_context **sock,
254                                    uint16_t *port)
255 {
256         NTSTATUS status = composite_wait(ctx);
257         if (NT_STATUS_IS_OK(status)) {
258                 struct connect_multi_state *multi =
259                         talloc_get_type(ctx->private_data,
260                                         struct connect_multi_state);
261                 *sock = talloc_steal(mem_ctx, multi->sock);
262                 *port = multi->result_port;
263         }
264         talloc_free(ctx);
265         return status;
266 }
267
268 NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx,
269                               const char *server_address,
270                               int num_server_ports, uint16_t *server_ports,
271                               struct resolve_context *resolve_ctx,
272                               struct event_context *event_ctx,
273                               struct socket_context **result,
274                               uint16_t *result_port)
275 {
276         struct composite_context *ctx =
277                 socket_connect_multi_send(mem_ctx, server_address,
278                                           num_server_ports, server_ports,
279                                           resolve_ctx,
280                                           event_ctx);
281         return socket_connect_multi_recv(ctx, mem_ctx, result, result_port);
282 }