s4: Implement UDP echo server example
[amitay/samba.git] / source4 / echo_server / echo_server.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Echo server service example
5
6    Copyright (C) 2010 Kai Blin  <kai@samba.org>
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "echo_server/echo_server.h"
24 /* Get at the config file settings */
25 #include "param/param.h"
26 /* This defines task_server_terminate */
27 #include "smbd/process_model.h"
28 /* We get load_interfaces from here */
29 #include "socket/netif.h"
30 /* NTSTATUS-related stuff */
31 #include "libcli/util/ntstatus.h"
32 /* tsocket-related functions */
33 #include "lib/tsocket/tsocket.h"
34
35 /* Structure to hold an echo server socket */
36 struct echo_socket {
37         /* This can come handy for the task struct in there */
38         struct echo_server *echo;
39         struct tsocket_address *local_address;
40 };
41
42 /* Structure to hold udp socket */
43 struct echo_udp_socket {
44         struct echo_socket *echo_socket;
45         struct tdgram_context *dgram;
46         struct tevent_queue *send_queue;
47 };
48
49 /*
50  * Main processing function.
51  *
52  * This is the start of the package processing.
53  * In the echo server it doesn't do much, but for more complicated servers,
54  * your code goes here (or at least is called from here.
55  */
56 static NTSTATUS echo_process(struct echo_server *echo,
57                              TALLOC_CTX *mem_ctx,
58                              DATA_BLOB *in,
59                              DATA_BLOB *out)
60 {
61         uint8_t *buf = talloc_memdup(mem_ctx, in->data, in->length);
62         NT_STATUS_HAVE_NO_MEMORY(buf);
63
64         out->data = buf;
65         out->length = in->length;
66
67         return NT_STATUS_OK;
68 }
69
70 /* Structure keeping track of a single UDP echo server call */
71 struct echo_udp_call {
72         /* The UDP packet came from here, our reply goes there as well */
73         struct tsocket_address *src;
74         DATA_BLOB in;
75         DATA_BLOB out;
76 };
77
78 /** Prototype of the send callback */
79 static void echo_udp_call_sendto_done(struct tevent_req *subreq);
80
81 /* Callback to receive UDP packets */
82 static void echo_udp_call_loop(struct tevent_req *subreq)
83 {
84         /*
85          * Our socket structure is the callback data. Get it in a
86          * type-safe way
87          */
88         struct echo_udp_socket *sock = tevent_req_callback_data(subreq,
89                                        struct echo_udp_socket);
90         struct echo_udp_call *call;
91         uint8_t *buf;
92         ssize_t len;
93         NTSTATUS status;
94         int sys_errno;
95
96         call = talloc(sock, struct echo_udp_call);
97         if (call == NULL) {
98                 goto done;
99         }
100
101         len = tdgram_recvfrom_recv(subreq, &sys_errno, call, &buf, &call->src);
102         TALLOC_FREE(subreq);
103         if (len == -1) {
104                 TALLOC_FREE(call);
105                 goto done;
106         }
107
108         call->in.data = buf;
109         call->in.length = len;
110
111         DEBUG(10, ("Received echo UDP packet of %lu bytes from %s\n",
112                   (long)len, tsocket_address_string(call->src, call)));
113
114         /* Handle the data coming in and compute the reply */
115         status = echo_process(sock->echo_socket->echo, call,
116                               &call->in, &call->out);
117         if (!NT_STATUS_IS_OK(status)) {
118                 TALLOC_FREE(call);
119                 DEBUG(0, ("echo_process returned %s\n",
120                           nt_errstr(status)));
121                 goto done;
122         }
123
124         /* I said the task struct would come in handy. */
125         subreq = tdgram_sendto_queue_send(call,
126                                 sock->echo_socket->echo->task->event_ctx,
127                                 sock->dgram,
128                                 sock->send_queue,
129                                 call->out.data,
130                                 call->out.length,
131                                 call->src);
132         if (subreq == NULL) {
133                 TALLOC_FREE(call);
134                 goto done;
135         }
136
137         tevent_req_set_callback(subreq, echo_udp_call_sendto_done, call);
138
139 done:
140         /* Now loop for the next incoming UDP packet, the async way */
141         subreq = tdgram_recvfrom_send(sock,
142                                 sock->echo_socket->echo->task->event_ctx,
143                                 sock->dgram);
144         if (subreq == NULL) {
145                 task_server_terminate(sock->echo_socket->echo->task,
146                                       "no memory for tdgram_recvfrom_send",
147                                       true);
148                 return;
149         }
150         tevent_req_set_callback(subreq, echo_udp_call_loop, sock);
151 }
152
153 /* Callback to send UDP replies */
154 static void echo_udp_call_sendto_done(struct tevent_req *subreq)
155 {
156         struct echo_udp_call *call = tevent_req_callback_data(subreq,
157                                      struct echo_udp_call);
158         ssize_t ret;
159         int sys_errno;
160
161         ret = tdgram_sendto_queue_recv(subreq, &sys_errno);
162
163         /*
164          * We don't actually care about the error, just get on with our life.
165          * We already set a new echo_udp_call_loop callback already, so we're
166          * almost done, just some memory to free.
167          */
168         TALLOC_FREE(call);
169 }
170
171 /* Start listening on a given address */
172 static NTSTATUS echo_add_socket(struct echo_server *echo,
173                                 const struct model_ops *ops,
174                                 const char *name,
175                                 const char *address,
176                                 uint16_t port)
177 {
178         struct echo_socket *echo_socket;
179         struct echo_udp_socket *echo_udp_socket;
180         struct tevent_req *udpsubreq;
181         NTSTATUS status;
182         int ret;
183
184         echo_socket = talloc(echo, struct echo_socket);
185         NT_STATUS_HAVE_NO_MEMORY(echo_socket);
186
187         echo_socket->echo = echo;
188
189         /*
190          * Initialize the tsocket_address.
191          * The nifty part is the "ip" string. This tells tsocket to autodetect
192          * ipv4 or ipv6 based on the IP address string passed.
193          */
194         ret = tsocket_address_inet_from_strings(echo_socket, "ip",
195                                                 address, port,
196                                                 &echo_socket->local_address);
197         if (ret != 0) {
198                 status = map_nt_error_from_unix(errno);
199                 return status;
200         }
201
202         /* Now set up the udp socket */
203         echo_udp_socket = talloc(echo_socket, struct echo_udp_socket);
204         NT_STATUS_HAVE_NO_MEMORY(echo_udp_socket);
205
206         echo_udp_socket->echo_socket = echo_socket;
207
208         ret = tdgram_inet_udp_socket(echo_socket->local_address,
209                                      NULL,
210                                      echo_udp_socket,
211                                      &echo_udp_socket->dgram);
212         if (ret != 0) {
213                 status = map_nt_error_from_unix(errno);
214                 DEBUG(0, ("Failed to bind to %s:%u UDP - %s\n",
215                           address, port, nt_errstr(status)));
216                 return status;
217         }
218
219         /*
220          * We set up a send queue so we can have multiple UDP packets in flight
221          */
222         echo_udp_socket->send_queue = tevent_queue_create(echo_udp_socket,
223                                                         "echo_udp_send_queue");
224         NT_STATUS_HAVE_NO_MEMORY(echo_udp_socket->send_queue);
225
226         /*
227          * To handle the UDP requests, set up a new tevent request as a
228          * subrequest of the current one.
229          */
230         udpsubreq = tdgram_recvfrom_send(echo_udp_socket,
231                                          echo->task->event_ctx,
232                                          echo_udp_socket->dgram);
233         NT_STATUS_HAVE_NO_MEMORY(udpsubreq);
234         tevent_req_set_callback(udpsubreq, echo_udp_call_loop, echo_udp_socket);
235
236         return NT_STATUS_OK;
237 }
238
239 /* Set up the listening sockets */
240 static NTSTATUS echo_startup_interfaces(struct echo_server *echo,
241                                         struct loadparm_context *lp_ctx,
242                                         struct interface *ifaces)
243 {
244         const struct model_ops *model_ops;
245         int num_interfaces;
246         TALLOC_CTX *tmp_ctx = talloc_new(echo);
247         NTSTATUS status;
248         int i;
249
250         /*
251          * Samba allows subtask to set their own process model.
252          * Available models currently are:
253          * - onefork  (forks exactly one child process)
254          * - prefork  (keep a couple of child processes around)
255          * - single   (only run a single process)
256          * - standard (fork one subprocess per incoming connection)
257          * - thread   (use threads instead of forks)
258          *
259          * For the echo server, the "single" process model works fine,
260          * you probably don't want to use the thread model unless you really
261          * know what you're doing.
262          */
263
264         model_ops = process_model_startup("single");
265         if (model_ops == NULL) {
266                 DEBUG(0, ("Can't find 'single' proces model_ops\n"));
267                 return NT_STATUS_INTERNAL_ERROR;
268         }
269
270         num_interfaces = iface_count(ifaces);
271
272         for(i=0; i<num_interfaces; i++) {
273                 const char *address = talloc_strdup(tmp_ctx, iface_n_ip(ifaces, i));
274
275                 status = echo_add_socket(echo, model_ops, "echo", address, ECHO_SERVICE_PORT);
276                 NT_STATUS_NOT_OK_RETURN(status);
277         }
278
279         TALLOC_FREE(tmp_ctx);
280         return NT_STATUS_OK;
281 }
282
283
284 /* Do the basic task initialization, check if the task should run */
285
286 static void echo_task_init(struct task_server *task)
287 {
288         struct interface *ifaces;
289         struct echo_server *echo;
290         NTSTATUS status;
291
292         /*
293          * For the purpose of the example, let's only start the server in DC
294          * and standalone modes, and not as a member server.
295          */
296         switch(lpcfg_server_role(task->lp_ctx)) {
297         case ROLE_STANDALONE:
298                 /* Yes, we want to run the echo server */
299                 break;
300         case ROLE_DOMAIN_MEMBER:
301                 task_server_terminate(task, "echo: Not starting echo server " \
302                                       "for domain members", false);
303                 return;
304         case ROLE_DOMAIN_CONTROLLER:
305                 /* Yes, we want to run the echo server */
306                 break;
307         }
308
309         load_interfaces(task, lpcfg_interfaces(task->lp_ctx), &ifaces);
310
311         if (iface_count(ifaces) == 0) {
312                 task_server_terminate(task,
313                                       "echo: No network interfaces configured",
314                                       false);
315                 return;
316         }
317
318         task_server_set_title(task, "task[echo]");
319
320         echo = talloc_zero(task, struct echo_server);
321         if (echo == NULL) {
322                 task_server_terminate(task, "echo: Out of memory", true);
323                 return;
324         }
325
326         echo->task = task;
327
328         status = echo_startup_interfaces(echo, task->lp_ctx, ifaces);
329         if (!NT_STATUS_IS_OK(status)) {
330                 task_server_terminate(task, "echo: Failed to set up interfaces",
331                                       true);
332                 return;
333         }
334 }
335
336 /*
337  * Register this server service with the main samba process.
338  *
339  * This is the function you need to put into the wscript_build file as
340  * init_function. All the real work happens in "echo_task_init" above.
341  */
342 NTSTATUS server_service_echo_init(void)
343 {
344         return register_server_service("echo", echo_task_init);
345 }