1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
14 */
15
16 #include <sys/types.h>
17 #include <sys/conf.h>
18 #include <sys/sysmacros.h>
19 #include <sys/cmn_err.h>
20 #include <sys/stat.h>
21 #include <sys/ddi.h>
22 #include <sys/sunddi.h>
23 #include <sys/proc.h>
24 #include <sys/class.h>
25 #include <sys/sdt.h>
26
27 #include <krrp_params.h>
28 #include <sys/krrp.h>
29
30 #include "krrp_server.h"
31
32 static void krrp_server_enable(krrp_server_t *server);
33 static void krrp_server_worker(void *void_server);
34 static int krrp_server_accept(krrp_server_t *server, ksocket_t *result_ks);
35 static int krrp_server_create_socket(krrp_server_t *server);
36 static void krrp_server_cleanup_socket(krrp_server_t *server);
37
38 void
39 krrp_server_create(krrp_server_t **result_server,
40 krrp_new_ks_cb_t *new_ks_cb, krrp_svr_error_cb_t *on_error_cb)
41 {
42 krrp_server_t *server;
43
44 VERIFY(result_server != NULL && *result_server == NULL);
45 VERIFY(new_ks_cb != NULL);
46 VERIFY(on_error_cb != NULL);
47
48 server = kmem_zalloc(sizeof (krrp_server_t), KM_SLEEP);
49
50 mutex_init(&server->mtx, NULL, MUTEX_DEFAULT, NULL);
51 cv_init(&server->cv, NULL, CV_DEFAULT, NULL);
52
53 server->new_ks_cb = new_ks_cb;
54 server->on_error_cb = on_error_cb;
55
56 *result_server = server;
57 }
58
59 void
60 krrp_server_destroy(krrp_server_t *server)
61 {
62 mutex_enter(&server->mtx);
63
64 server->running = B_FALSE;
65 cv_signal(&server->cv);
66 krrp_server_cleanup_socket(server);
67 mutex_exit(&server->mtx);
68
69 if (server->t_did != 0)
70 thread_join(server->t_did);
71
72 cv_destroy(&server->cv);
73 mutex_destroy(&server->mtx);
74
75 kmem_free(server, sizeof (krrp_server_t));
76 }
77
78 boolean_t
79 krrp_server_is_running(krrp_server_t *server)
80 {
81 boolean_t result = B_TRUE;
82
83 mutex_enter(&server->mtx);
84
85 if (!server->running || server->state != KRRP_SRVS_ACTIVE)
86 result = B_FALSE;
87
88 mutex_exit(&server->mtx);
89
90 return (result);
91 }
92
93 int
94 krrp_server_set_config(krrp_server_t *server, nvlist_t *params,
95 krrp_error_t *error)
96 {
97 int rc = 0;
98 const char *listening_addr = NULL;
99 int listening_port = 0;
100 krrp_server_state_t saved_srv_state;
101
102 mutex_enter(&server->mtx);
103
104 if (server->state == KRRP_SRVS_RECONFIGURE) {
105 krrp_error_set(error, KRRP_ERRNO_BUSY, 0);
106 mutex_exit(&server->mtx);
107 return (-1);
108 }
109
110 saved_srv_state = server->state;
111 server->state = KRRP_SRVS_RECONFIGURE;
112 mutex_exit(&server->mtx);
113
114 (void) krrp_param_get(KRRP_PARAM_LISTENING_ADDRESS,
115 params, (void *)&listening_addr);
116
117 if (listening_addr != NULL && (listening_addr[0] == '\0' ||
118 (strlen(listening_addr) >= sizeof (server->listening_addr)))) {
119 krrp_error_set(error, KRRP_ERRNO_ADDR, EINVAL);
120 rc = -1;
121 goto out;
122 }
123
124 rc = krrp_param_get(KRRP_PARAM_PORT,
125 params, &listening_port);
126 if (rc != 0) {
127 krrp_error_set(error, KRRP_ERRNO_PORT, ENOENT);
128 goto out;
129 }
130
131 if (listening_port < KRRP_MIN_PORT ||
132 listening_port > KRRP_MAX_PORT) {
133 krrp_error_set(error, KRRP_ERRNO_PORT, EINVAL);
134 rc = -1;
135 goto out;
136 }
137
138 mutex_enter(&server->mtx);
139
140 (void) memset(server->listening_addr, 0,
141 sizeof (server->listening_addr));
142 if (listening_addr != NULL)
143 (void) strncpy(server->listening_addr, listening_addr,
144 sizeof (server->listening_addr));
145
146 server->listening_port = listening_port;
147
148 if (!server->running) {
149 /* The server is not started yet, so need to enable it */
150 krrp_server_enable(server);
151 } else {
152 /*
153 * The server is started, so need to close the current socket,
154 * to recreate it with new parameters
155 */
156 krrp_server_cleanup_socket(server);
157 }
158
159 /*
160 * To exclude double-error in userspace
161 */
162 server->without_event = B_TRUE;
163
164 /*
165 * The worker thread may waits on cv right after start and
166 * if an error occured so need to wake up it.
167 */
168 cv_signal(&server->cv);
169
170 /*
171 * We have woken up the worker thread so wait the result of
172 * socket-create operation
173 */
174 while (server->state == KRRP_SRVS_RECONFIGURE)
175 cv_wait(&server->cv, &server->mtx);
176
177 if (server->state != KRRP_SRVS_ACTIVE) {
178 krrp_error_set(error, server->error.krrp_errno,
179 server->error.unix_errno);
180 rc = -1;
181 }
182
183 server->without_event = B_FALSE;
184 mutex_exit(&server->mtx);
185
186 return (rc);
187
188 out:
189 mutex_enter(&server->mtx);
190 server->state = saved_srv_state;
191 mutex_exit(&server->mtx);
192 return (-1);
193 }
194
195 int
196 krrp_server_get_config(krrp_server_t *server, nvlist_t *result,
197 krrp_error_t *error)
198 {
199 int rc = -1;
200
201 VERIFY(result != NULL);
202
203 mutex_enter(&server->mtx);
204
205 if (!server->running) {
206 krrp_error_set(error, KRRP_ERRNO_INVAL, 0);
207 goto out;
208 }
209
210 (void) krrp_param_put(KRRP_PARAM_PORT, result,
211 &server->listening_port);
212
213 if (server->listening_addr[0] != '\0')
214 (void) krrp_param_put(KRRP_PARAM_LISTENING_ADDRESS,
215 result, server->listening_addr);
216
217 rc = 0;
218
219 out:
220 mutex_exit(&server->mtx);
221
222 return (rc);
223 }
224
225 static void
226 krrp_server_enable(krrp_server_t *server)
227 {
228 VERIFY(MUTEX_HELD(&server->mtx));
229
230 /* thread_create never fails */
231 (void) thread_create(NULL, 0, &krrp_server_worker,
232 server, 0, &p0, TS_RUN, minclsyspri);
233
234 while (server->t_did == 0)
235 cv_wait(&server->cv, &server->mtx);
236 }
237
238 static void
239 krrp_server_worker(void *void_server)
240 {
241 krrp_server_t *server = void_server;
242 ksocket_t ks = NULL;
243
244 mutex_enter(&server->mtx);
245 server->running = B_TRUE;
246 server->t_did = curthread->t_did;
247 cv_signal(&server->cv);
248 cv_wait(&server->cv, &server->mtx);
249
250 while (server->running) {
251 if (krrp_server_create_socket(server) != 0) {
252 if (!server->without_event)
253 server->on_error_cb(&server->error);
254
255 server->state = KRRP_SRVS_IN_ERROR;
256 cv_signal(&server->cv);
257 cv_wait(&server->cv, &server->mtx);
258 continue;
259 }
260
261 server->state = KRRP_SRVS_ACTIVE;
262 cv_signal(&server->cv);
263 mutex_exit(&server->mtx);
264
265 /*
266 * To be able to unblock ksocket_accept from another
267 * thread by closing the ksocket
268 */
269 ksocket_hold(server->listening_ks);
270
271 while (krrp_server_accept(server, &ks) == 0)
272 server->new_ks_cb(ks);
273
274 ksocket_rele(server->listening_ks);
275
276 mutex_enter(&server->mtx);
277 }
278
279 mutex_exit(&server->mtx);
280 }
281
282 static int
283 krrp_server_accept(krrp_server_t *server, ksocket_t *result_ks)
284 {
285 int rc = 0;
286
287 repeat:
288 rc = ksocket_accept(server->listening_ks, NULL, NULL,
289 result_ks, CRED());
290 if (rc == ECONNABORTED || rc == EINTR)
291 goto repeat;
292
293 /*
294 * ENOTSOCK means someone call ksocket_close()
295 * for the listening socket
296 */
297 if (rc != 0 && rc != ENOTSOCK) {
298 cmn_err(CE_WARN, "Failed to accept new socket "
299 "[errno: %d]", rc);
300 }
301
302 return (rc);
303 }
304
305 static int
306 krrp_server_create_socket(krrp_server_t *server)
307 {
308 int rc;
309 uint32_t on = 1;
310 struct sockaddr_in servaddr;
311
312 krrp_error_init(&server->error);
313
314 krrp_server_cleanup_socket(server);
315
316 rc = ksocket_socket(&server->listening_ks, AF_INET, SOCK_STREAM,
317 0, KSOCKET_SLEEP, CRED());
318 if (rc != 0) {
319 krrp_error_set(&server->error, KRRP_ERRNO_CREATEFAIL, rc);
320 goto out;
321 }
322
323 (void) ksocket_setsockopt(server->listening_ks, SOL_SOCKET,
324 SO_REUSEADDR, &on, sizeof (on), CRED());
325
326 servaddr.sin_family = AF_INET;
327 servaddr.sin_port = htons(server->listening_port);
328
329 if (server->listening_addr[0] != '\0') {
330 if (inet_pton(AF_INET, (char *)server->listening_addr,
331 &servaddr.sin_addr) != 1) {
332 krrp_error_set(&server->error, KRRP_ERRNO_ADDR, EINVAL);
333 rc = -1;
334 goto fini;
335 }
336 } else
337 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
338
339 rc = ksocket_bind(server->listening_ks, (struct sockaddr *)&servaddr,
340 sizeof (servaddr), CRED());
341 if (rc != 0) {
342 krrp_error_set(&server->error, KRRP_ERRNO_BINDFAIL, rc);
343 goto fini;
344 }
345
346 rc = ksocket_listen(server->listening_ks, 5, CRED());
347 if (rc < 0)
348 krrp_error_set(&server->error, KRRP_ERRNO_LISTENFAIL, rc);
349
350 fini:
351 if (rc != 0)
352 krrp_server_cleanup_socket(server);
353
354 out:
355 return (rc);
356 }
357
358 static void
359 krrp_server_cleanup_socket(krrp_server_t *server)
360 {
361 if (server->listening_ks != NULL) {
362 (void) ksocket_close(server->listening_ks, CRED());
363 server->listening_ks = NULL;
364 }
365 }