r11823: make the socket_connect_send() context a child of the local state
[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 2 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, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26 #include "lib/socket/socket.h"
27 #include "lib/events/events.h"
28 #include "libcli/composite/composite.h"
29 #include "librpc/gen_ndr/nbt.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 socket_context *sock;
42         uint16_t result_port;
43
44         int num_connects_sent, num_connects_recv;
45 };
46
47 /*
48   state of an individual socket_connect_send() call
49 */
50 struct connect_one_state {
51         struct composite_context *result;
52         struct socket_context *sock;
53         uint16_t port;
54 };
55
56 static void continue_resolve_name(struct composite_context *creq);
57 static void connect_multi_timer(struct event_context *ev,
58                                     struct timed_event *te,
59                                     struct timeval tv, void *p);
60 static void connect_multi_next_socket(struct composite_context *result);
61 static void continue_one(struct composite_context *creq);
62
63 /*
64   setup an async socket_connect, with multiple ports
65 */
66 struct composite_context *socket_connect_multi_send(TALLOC_CTX *mem_ctx,
67                                                     const char *server_address,
68                                                     int num_server_ports,
69                                                     uint16_t *server_ports,
70                                                     struct event_context *event_ctx)
71 {
72         struct composite_context *result;
73         struct connect_multi_state *multi;
74         int i;
75
76         result = talloc_zero(mem_ctx, struct composite_context);
77         if (result == NULL) return NULL;
78         result->state = COMPOSITE_STATE_IN_PROGRESS;
79         result->event_ctx = event_ctx;
80
81         multi = talloc_zero(result, struct connect_multi_state);
82         if (composite_nomem(multi, result)) goto failed;
83         result->private_data = multi;
84
85         multi->server_address = talloc_strdup(multi, server_address);
86         if (composite_nomem(multi->server_address, result)) goto failed;
87
88         multi->num_ports = num_server_ports;
89         multi->ports = talloc_array(multi, uint16_t, multi->num_ports);
90         if (composite_nomem(multi->ports, result)) goto failed;
91
92         for (i=0; i<multi->num_ports; i++) {
93                 multi->ports[i] = server_ports[i];
94         }
95
96         if (!is_ipaddress(server_address)) {
97                 /*  
98                     we don't want to do the name resolution separately
99                     for each port, so start it now, then only start on
100                     the real sockets once we have an IP
101                  */
102                 struct nbt_name name;
103                 struct composite_context *creq;
104                 make_nbt_name_client(&name, server_address);
105                 creq = resolve_name_send(&name, result->event_ctx,
106                                          lp_name_resolve_order());
107                 if (composite_nomem(creq, result)) goto failed;
108                 composite_continue(result, creq, continue_resolve_name, result);
109                 return result;
110         }
111
112         /* now we've setup the state we can process the first socket */
113         connect_multi_next_socket(result);
114
115         if (!NT_STATUS_IS_OK(result->status)) {
116                 goto failed;
117         }
118
119         return result;
120
121  failed:
122         composite_trigger_error(result);
123         return result;
124 }
125
126 /*
127   start connecting to the next socket/port in the list
128 */
129 static void connect_multi_next_socket(struct composite_context *result)
130 {
131         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
132                                                             struct connect_multi_state);
133         struct connect_one_state *state;
134         struct composite_context *creq;
135         int next = multi->num_connects_sent;
136
137         if (next == multi->num_ports) {
138                 /* don't do anything, just wait for the existing ones to finish */
139                 return;
140         }
141
142         multi->num_connects_sent += 1;
143
144         state = talloc(multi, struct connect_one_state);
145         if (composite_nomem(state, result)) return;
146
147         state->result = result;
148         state->port = multi->ports[next];
149
150         result->status = socket_create("ipv4", SOCKET_TYPE_STREAM, &state->sock, 0);
151         if (!composite_is_ok(result)) return;
152
153         talloc_steal(state, state->sock);
154
155         creq = socket_connect_send(state->sock, NULL, 0, 
156                                    multi->server_address, state->port, 0, result->event_ctx);
157         if (composite_nomem(creq, result)) return;
158         talloc_steal(state, creq);
159
160         composite_continue(result, creq, continue_one, state);
161
162         /* if there are more ports to go then setup a timer to fire when we have waited
163            for a couple of milli-seconds, when that goes off we try the next port regardless
164            of whether this port has completed */
165         if (multi->num_ports > multi->num_connects_sent) {
166                 /* note that this timer is a child of the single
167                    connect attempt state, so it will go away when this
168                    request completes */
169                 event_add_timed(result->event_ctx, state,
170                                 timeval_current_ofs(0, MULTI_PORT_DELAY),
171                                 connect_multi_timer, result);
172         }
173 }
174
175 /*
176   a timer has gone off telling us that we should try the next port
177 */
178 static void connect_multi_timer(struct event_context *ev,
179                                 struct timed_event *te,
180                                 struct timeval tv, void *p)
181 {
182         struct composite_context *result = talloc_get_type(p, struct composite_context);
183         connect_multi_next_socket(result);
184 }
185
186
187 /*
188   recv name resolution reply then send the next connect
189 */
190 static void continue_resolve_name(struct composite_context *creq)
191 {
192         struct composite_context *result = talloc_get_type(creq->async.private_data, 
193                                                            struct composite_context);
194         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
195                                                             struct connect_multi_state);
196         const char *addr;
197
198         result->status = resolve_name_recv(creq, multi, &addr);
199         if (!composite_is_ok(result)) return;
200
201         multi->server_address = addr;
202
203         connect_multi_next_socket(result);
204 }
205
206 /*
207   one of our socket_connect_send() calls hash finished. If it got a
208   connection or there are none left then we are done
209 */
210 static void continue_one(struct composite_context *creq)
211 {
212         struct connect_one_state *state = talloc_get_type(creq->async.private_data, 
213                                                           struct connect_one_state);
214         struct composite_context *result = state->result;
215         struct connect_multi_state *multi = talloc_get_type(result->private_data, 
216                                                             struct connect_multi_state);
217         NTSTATUS status;
218         multi->num_connects_recv++;
219
220         status = socket_connect_recv(creq);
221
222         if (NT_STATUS_IS_OK(status)) {
223                 multi->sock = talloc_steal(multi, state->sock);
224                 multi->result_port = state->port;
225         }
226
227         talloc_free(state);
228
229         if (NT_STATUS_IS_OK(status) || 
230             multi->num_connects_recv == multi->num_ports) {
231                 result->status = status;
232                 composite_done(result);
233                 return;
234         }
235
236         /* try the next port */
237         connect_multi_next_socket(result);
238 }
239
240 /*
241   async recv routine for socket_connect_multi()
242  */
243 NTSTATUS socket_connect_multi_recv(struct composite_context *ctx,
244                                    TALLOC_CTX *mem_ctx,
245                                    struct socket_context **sock,
246                                    uint16_t *port)
247 {
248         NTSTATUS status = composite_wait(ctx);
249         if (NT_STATUS_IS_OK(status)) {
250                 struct connect_multi_state *multi =
251                         talloc_get_type(ctx->private_data,
252                                         struct connect_multi_state);
253                 *sock = talloc_steal(mem_ctx, multi->sock);
254                 *port = multi->result_port;
255         }
256         talloc_free(ctx);
257         return status;
258 }
259
260 NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx,
261                               const char *server_address,
262                               int num_server_ports, uint16_t *server_ports,
263                               struct event_context *event_ctx,
264                               struct socket_context **result,
265                               uint16_t *result_port)
266 {
267         struct composite_context *ctx =
268                 socket_connect_multi_send(mem_ctx, server_address,
269                                           num_server_ports, server_ports,
270                                           event_ctx);
271         return socket_connect_multi_recv(ctx, mem_ctx, result, result_port);
272 }