58ab673965395af95f45a68d6361b9e783cb2abd
[samba-svnmirror.git] / source / 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         const char **name_resolve_order;
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                                                     const char **name_resolve_order,
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->name_resolve_order = str_list_copy(multi, name_resolve_order);
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(&name, result->event_ctx,
111                                          name_resolve_order);
112                 if (composite_nomem(creq, result)) goto failed;
113                 composite_continue(result, creq, continue_resolve_name, result);
114                 return result;
115         }
116
117         /* now we've setup the state we can process the first socket */
118         connect_multi_next_socket(result);
119
120         if (!NT_STATUS_IS_OK(result->status)) {
121                 goto failed;
122         }
123
124         return result;
125
126  failed:
127         composite_error(result, result->status);
128         return result;
129 }
130
131 /*
132   start connecting to the next socket/port in the list
133 */
134 static void connect_multi_next_socket(struct composite_context *result)
135 {
136         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
137                                                             struct connect_multi_state);
138         struct connect_one_state *state;
139         struct composite_context *creq;
140         int next = multi->num_connects_sent;
141
142         if (next == multi->num_ports) {
143                 /* don't do anything, just wait for the existing ones to finish */
144                 return;
145         }
146
147         multi->num_connects_sent += 1;
148
149         state = talloc(multi, struct connect_one_state);
150         if (composite_nomem(state, result)) return;
151
152         state->result = result;
153         result->status = socket_create("ipv4", SOCKET_TYPE_STREAM, &state->sock, 0);
154         if (!composite_is_ok(result)) return;
155
156         /* Form up the particular address we are interested in */
157         state->addr = socket_address_from_strings(state, state->sock->backend_name, 
158                                                   multi->server_address, multi->ports[next]);
159         if (composite_nomem(state->addr, result)) return;
160
161         talloc_steal(state, state->sock);
162
163         creq = socket_connect_send(state->sock, NULL, 
164                                    state->addr, 0, multi->name_resolve_order, 
165                                    result->event_ctx);
166         if (composite_nomem(creq, result)) return;
167         talloc_steal(state, creq);
168
169         composite_continue(result, creq, continue_one, state);
170
171         /* if there are more ports to go then setup a timer to fire when we have waited
172            for a couple of milli-seconds, when that goes off we try the next port regardless
173            of whether this port has completed */
174         if (multi->num_ports > multi->num_connects_sent) {
175                 /* note that this timer is a child of the single
176                    connect attempt state, so it will go away when this
177                    request completes */
178                 event_add_timed(result->event_ctx, state,
179                                 timeval_current_ofs(0, MULTI_PORT_DELAY),
180                                 connect_multi_timer, result);
181         }
182 }
183
184 /*
185   a timer has gone off telling us that we should try the next port
186 */
187 static void connect_multi_timer(struct event_context *ev,
188                                 struct timed_event *te,
189                                 struct timeval tv, void *p)
190 {
191         struct composite_context *result = talloc_get_type(p, struct composite_context);
192         connect_multi_next_socket(result);
193 }
194
195
196 /*
197   recv name resolution reply then send the next connect
198 */
199 static void continue_resolve_name(struct composite_context *creq)
200 {
201         struct composite_context *result = talloc_get_type(creq->async.private_data, 
202                                                            struct composite_context);
203         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
204                                                             struct connect_multi_state);
205         const char *addr;
206
207         result->status = resolve_name_recv(creq, multi, &addr);
208         if (!composite_is_ok(result)) return;
209
210         multi->server_address = addr;
211
212         connect_multi_next_socket(result);
213 }
214
215 /*
216   one of our socket_connect_send() calls hash finished. If it got a
217   connection or there are none left then we are done
218 */
219 static void continue_one(struct composite_context *creq)
220 {
221         struct connect_one_state *state = talloc_get_type(creq->async.private_data, 
222                                                           struct connect_one_state);
223         struct composite_context *result = state->result;
224         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
225                                                             struct connect_multi_state);
226         NTSTATUS status;
227         multi->num_connects_recv++;
228
229         status = socket_connect_recv(creq);
230
231         if (NT_STATUS_IS_OK(status)) {
232                 multi->sock = talloc_steal(multi, state->sock);
233                 multi->result_port = state->addr->port;
234         }
235
236         talloc_free(state);
237
238         if (NT_STATUS_IS_OK(status) || 
239             multi->num_connects_recv == multi->num_ports) {
240                 result->status = status;
241                 composite_done(result);
242                 return;
243         }
244
245         /* try the next port */
246         connect_multi_next_socket(result);
247 }
248
249 /*
250   async recv routine for socket_connect_multi()
251  */
252 _PUBLIC_ NTSTATUS socket_connect_multi_recv(struct composite_context *ctx,
253                                    TALLOC_CTX *mem_ctx,
254                                    struct socket_context **sock,
255                                    uint16_t *port)
256 {
257         NTSTATUS status = composite_wait(ctx);
258         if (NT_STATUS_IS_OK(status)) {
259                 struct connect_multi_state *multi =
260                         talloc_get_type(ctx->private_data,
261                                         struct connect_multi_state);
262                 *sock = talloc_steal(mem_ctx, multi->sock);
263                 *port = multi->result_port;
264         }
265         talloc_free(ctx);
266         return status;
267 }
268
269 NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx,
270                               const char *server_address,
271                               int num_server_ports, uint16_t *server_ports,
272                               const char **name_resolve_order,
273                               struct event_context *event_ctx,
274                               struct socket_context **result,
275                               uint16_t *result_port)
276 {
277         struct composite_context *ctx =
278                 socket_connect_multi_send(mem_ctx, server_address,
279                                           num_server_ports, server_ports,
280                                           name_resolve_order,
281                                           event_ctx);
282         return socket_connect_multi_recv(ctx, mem_ctx, result, result_port);
283 }