sp_common.c revision 1.16 1 /* $NetBSD: sp_common.c,v 1.16 2010/12/16 12:38:20 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2010 Antti Kantee. All Rights Reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 /*
29 * Common client/server sysproxy routines. #included.
30 */
31
32 #include <sys/cdefs.h>
33
34 #include <sys/types.h>
35 #include <sys/mman.h>
36 #include <sys/queue.h>
37 #include <sys/socket.h>
38 #include <sys/un.h>
39 #include <sys/syslimits.h>
40
41 #include <arpa/inet.h>
42 #include <netinet/in.h>
43 #include <netinet/tcp.h>
44
45 #include <assert.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <inttypes.h>
49 #include <poll.h>
50 #include <pthread.h>
51 #include <stdarg.h>
52 #include <stddef.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57
58 //#define DEBUG
59 #ifdef DEBUG
60 #define DPRINTF(x) mydprintf x
61 static void
62 mydprintf(const char *fmt, ...)
63 {
64 va_list ap;
65
66 va_start(ap, fmt);
67 vfprintf(stderr, fmt, ap);
68 va_end(ap);
69 }
70 #else
71 #define DPRINTF(x)
72 #endif
73
74 /*
75 * Bah, I hate writing on-off-wire conversions in C
76 */
77
78 enum { RUMPSP_REQ, RUMPSP_RESP, RUMPSP_ERROR };
79 enum { RUMPSP_SYSCALL,
80 RUMPSP_COPYIN, RUMPSP_COPYINSTR,
81 RUMPSP_COPYOUT, RUMPSP_COPYOUTSTR,
82 RUMPSP_ANONMMAP };
83
84 struct rsp_hdr {
85 uint64_t rsp_len;
86 uint64_t rsp_reqno;
87 uint16_t rsp_class;
88 uint16_t rsp_type;
89 /*
90 * We want this structure 64bit-aligned for typecast fun,
91 * so might as well use the following for something.
92 */
93 union {
94 uint32_t sysnum;
95 uint32_t error;
96 } u;
97 };
98 #define HDRSZ sizeof(struct rsp_hdr)
99 #define rsp_sysnum u.sysnum
100 #define rsp_error u.error
101
102 #define MAXBANNER 96
103
104 /*
105 * Data follows the header. We have two types of structured data.
106 */
107
108 /* copyin/copyout */
109 struct rsp_copydata {
110 size_t rcp_len;
111 void *rcp_addr;
112 uint8_t rcp_data[0];
113 };
114
115 /* syscall response */
116 struct rsp_sysresp {
117 int rsys_error;
118 register_t rsys_retval[2];
119 };
120
121 struct respwait {
122 uint64_t rw_reqno;
123 void *rw_data;
124 size_t rw_dlen;
125 int rw_error;
126
127 pthread_cond_t rw_cv;
128
129 TAILQ_ENTRY(respwait) rw_entries;
130 };
131
132 struct spclient {
133 int spc_fd;
134 int spc_refcnt;
135 int spc_dying;
136
137 pthread_mutex_t spc_mtx;
138 pthread_cond_t spc_cv;
139
140 struct lwp *spc_mainlwp;
141 pid_t spc_pid;
142
143 TAILQ_HEAD(, respwait) spc_respwait;
144
145 /* rest of the fields are zeroed upon disconnect */
146 #define SPC_ZEROFF offsetof(struct spclient, spc_pfd)
147 struct pollfd *spc_pfd;
148
149 struct rsp_hdr spc_hdr;
150 uint8_t *spc_buf;
151 size_t spc_off;
152
153 uint64_t spc_nextreq;
154 int spc_ostatus, spc_istatus;
155 };
156 #define SPCSTATUS_FREE 0
157 #define SPCSTATUS_BUSY 1
158 #define SPCSTATUS_WANTED 2
159
160 typedef int (*addrparse_fn)(const char *, struct sockaddr **, int);
161 typedef int (*connecthook_fn)(int);
162 typedef void (*cleanup_fn)(struct sockaddr *);
163
164 static int readframe(struct spclient *);
165 static void handlereq(struct spclient *);
166
167 static __inline void
168 spcresetbuf(struct spclient *spc)
169 {
170
171 spc->spc_buf = NULL;
172 spc->spc_off = 0;
173 }
174
175 static __inline void
176 spcfreebuf(struct spclient *spc)
177 {
178
179 free(spc->spc_buf);
180 spcresetbuf(spc);
181 }
182
183 static void
184 sendlockl(struct spclient *spc)
185 {
186
187 /* assert(pthread_mutex_owned) */
188 while (spc->spc_ostatus != SPCSTATUS_FREE) {
189 spc->spc_ostatus = SPCSTATUS_WANTED;
190 pthread_cond_wait(&spc->spc_cv, &spc->spc_mtx);
191 }
192 spc->spc_ostatus = SPCSTATUS_BUSY;
193 }
194
195 static void
196 sendlock(struct spclient *spc)
197 {
198
199 pthread_mutex_lock(&spc->spc_mtx);
200 sendlockl(spc);
201 pthread_mutex_unlock(&spc->spc_mtx);
202 }
203
204 static void
205 sendunlockl(struct spclient *spc)
206 {
207
208 /* assert(pthread_mutex_owned) */
209 if (spc->spc_ostatus == SPCSTATUS_WANTED)
210 pthread_cond_broadcast(&spc->spc_cv);
211 spc->spc_ostatus = SPCSTATUS_FREE;
212 }
213
214 static void
215 sendunlock(struct spclient *spc)
216 {
217
218 pthread_mutex_lock(&spc->spc_mtx);
219 sendunlockl(spc);
220 pthread_mutex_unlock(&spc->spc_mtx);
221 }
222
223 static int
224 dosend(struct spclient *spc, const void *data, size_t dlen)
225 {
226 struct pollfd pfd;
227 const uint8_t *sdata = data;
228 ssize_t n;
229 size_t sent;
230 int fd = spc->spc_fd;
231
232 pfd.fd = fd;
233 pfd.events = POLLOUT;
234
235 for (sent = 0, n = 0; sent < dlen; ) {
236 if (n) {
237 if (poll(&pfd, 1, INFTIM) == -1) {
238 if (errno == EINTR)
239 continue;
240 return errno;
241 }
242 }
243
244 n = send(fd, sdata + sent, dlen - sent, MSG_NOSIGNAL);
245 if (n == 0) {
246 return EFAULT;
247 }
248 if (n == -1) {
249 if (errno != EAGAIN)
250 return EFAULT;
251 continue;
252 }
253 sent += n;
254 }
255
256 return 0;
257 }
258
259 static void
260 putwait(struct spclient *spc, struct respwait *rw, struct rsp_hdr *rhdr)
261 {
262
263 rw->rw_data = NULL;
264 rw->rw_dlen = 0;
265 pthread_cond_init(&rw->rw_cv, NULL);
266
267 pthread_mutex_lock(&spc->spc_mtx);
268 rw->rw_reqno = rhdr->rsp_reqno = spc->spc_nextreq++;
269 TAILQ_INSERT_TAIL(&spc->spc_respwait, rw, rw_entries);
270
271 sendlockl(spc);
272 }
273
274 static void
275 unputwait(struct spclient *spc, struct respwait *rw)
276 {
277
278 sendunlockl(spc);
279
280 TAILQ_REMOVE(&spc->spc_respwait, rw, rw_entries);
281 pthread_mutex_unlock(&spc->spc_mtx);
282 pthread_cond_destroy(&rw->rw_cv);
283 }
284
285 static void
286 kickwaiter(struct spclient *spc)
287 {
288 struct respwait *rw;
289 int error;
290
291 pthread_mutex_lock(&spc->spc_mtx);
292 TAILQ_FOREACH(rw, &spc->spc_respwait, rw_entries) {
293 if (rw->rw_reqno == spc->spc_hdr.rsp_reqno)
294 break;
295 }
296 if (rw == NULL) {
297 DPRINTF(("no waiter found, invalid reqno %" PRIu64 "?\n",
298 spc->spc_hdr.rsp_reqno));
299 return;
300 }
301 DPRINTF(("rump_sp: client %p woke up waiter at %p\n", spc, rw));
302 rw->rw_data = spc->spc_buf;
303 rw->rw_dlen = (size_t)(spc->spc_off - HDRSZ);
304 if (spc->spc_hdr.rsp_class == RUMPSP_ERROR) {
305 error = rw->rw_error = spc->spc_hdr.rsp_error;
306 } else {
307 error = rw->rw_error = 0;
308 }
309 pthread_cond_signal(&rw->rw_cv);
310 pthread_mutex_unlock(&spc->spc_mtx);
311
312 if (error)
313 spcfreebuf(spc);
314 else
315 spcresetbuf(spc);
316 }
317
318 static void
319 kickall(struct spclient *spc)
320 {
321 struct respwait *rw;
322
323 /* DIAGASSERT(mutex_owned(spc_lock)) */
324 TAILQ_FOREACH(rw, &spc->spc_respwait, rw_entries)
325 pthread_cond_broadcast(&rw->rw_cv);
326 }
327
328 static int
329 waitresp(struct spclient *spc, struct respwait *rw)
330 {
331 struct pollfd pfd;
332 int rv = 0;
333
334 sendunlockl(spc);
335
336 rw->rw_error = 0;
337 while (rw->rw_data == NULL && rw->rw_error == 0 && spc->spc_dying == 0){
338 /* are we free to receive? */
339 if (spc->spc_istatus == SPCSTATUS_FREE) {
340 int gotresp;
341
342 spc->spc_istatus = SPCSTATUS_BUSY;
343 pthread_mutex_unlock(&spc->spc_mtx);
344
345 pfd.fd = spc->spc_fd;
346 pfd.events = POLLIN;
347
348 for (gotresp = 0; !gotresp; ) {
349 switch (readframe(spc)) {
350 case 0:
351 poll(&pfd, 1, INFTIM);
352 continue;
353 case -1:
354 rv = errno;
355 spc->spc_dying = 1;
356 goto cleanup;
357 default:
358 break;
359 }
360
361 switch (spc->spc_hdr.rsp_class) {
362 case RUMPSP_RESP:
363 case RUMPSP_ERROR:
364 kickwaiter(spc);
365 gotresp = spc->spc_hdr.rsp_reqno ==
366 rw->rw_reqno;
367 break;
368 case RUMPSP_REQ:
369 handlereq(spc);
370 break;
371 default:
372 /* panic */
373 break;
374 }
375 }
376 cleanup:
377 pthread_mutex_lock(&spc->spc_mtx);
378 if (spc->spc_istatus == SPCSTATUS_WANTED)
379 kickall(spc);
380 spc->spc_istatus = SPCSTATUS_FREE;
381 } else {
382 spc->spc_istatus = SPCSTATUS_WANTED;
383 pthread_cond_wait(&rw->rw_cv, &spc->spc_mtx);
384 }
385 }
386
387 TAILQ_REMOVE(&spc->spc_respwait, rw, rw_entries);
388 pthread_mutex_unlock(&spc->spc_mtx);
389
390 pthread_cond_destroy(&rw->rw_cv);
391
392 if (rv)
393 return rv;
394 if (spc->spc_dying)
395 return ENOTCONN;
396 return rw->rw_error;
397 }
398
399 static int
400 readframe(struct spclient *spc)
401 {
402 int fd = spc->spc_fd;
403 size_t left;
404 size_t framelen;
405 ssize_t n;
406
407 /* still reading header? */
408 if (spc->spc_off < HDRSZ) {
409 DPRINTF(("rump_sp: readframe getting header at offset %zu\n",
410 spc->spc_off));
411
412 left = HDRSZ - spc->spc_off;
413 /*LINTED: cast ok */
414 n = read(fd, (uint8_t *)&spc->spc_hdr + spc->spc_off, left);
415 if (n == 0) {
416 return -1;
417 }
418 if (n == -1) {
419 if (errno == EAGAIN)
420 return 0;
421 return -1;
422 }
423
424 spc->spc_off += n;
425 if (spc->spc_off < HDRSZ)
426 return -1;
427
428 /*LINTED*/
429 framelen = spc->spc_hdr.rsp_len;
430
431 if (framelen < HDRSZ) {
432 return -1;
433 } else if (framelen == HDRSZ) {
434 return 1;
435 }
436
437 spc->spc_buf = malloc(framelen - HDRSZ);
438 if (spc->spc_buf == NULL) {
439 return -1;
440 }
441 memset(spc->spc_buf, 0, framelen - HDRSZ);
442
443 /* "fallthrough" */
444 } else {
445 /*LINTED*/
446 framelen = spc->spc_hdr.rsp_len;
447 }
448
449 left = framelen - spc->spc_off;
450
451 DPRINTF(("rump_sp: readframe getting body at offset %zu, left %zu\n",
452 spc->spc_off, left));
453
454 if (left == 0)
455 return 1;
456 n = read(fd, spc->spc_buf + (spc->spc_off - HDRSZ), left);
457 if (n == 0) {
458 return -1;
459 }
460 if (n == -1) {
461 if (errno == EAGAIN)
462 return 0;
463 return -1;
464 }
465 spc->spc_off += n;
466 left -= n;
467
468 /* got everything? */
469 if (left == 0)
470 return 1;
471 else
472 return 0;
473 }
474
475 static int
476 tcp_parse(const char *addr, struct sockaddr **sa, int allow_wildcard)
477 {
478 struct sockaddr_in sin;
479 char buf[64];
480 const char *p;
481 size_t l;
482 int port;
483
484 memset(&sin, 0, sizeof(sin));
485 sin.sin_len = sizeof(sin);
486 sin.sin_family = AF_INET;
487
488 p = strchr(addr, ':');
489 if (!p) {
490 fprintf(stderr, "rump_sp_tcp: missing port specifier\n");
491 return EINVAL;
492 }
493
494 l = p - addr;
495 if (l > sizeof(buf)-1) {
496 fprintf(stderr, "rump_sp_tcp: address too long\n");
497 return EINVAL;
498 }
499 strncpy(buf, addr, l);
500 buf[l] = '\0';
501
502 /* special INADDR_ANY treatment */
503 if (strcmp(buf, "*") == 0 || strcmp(buf, "0") == 0) {
504 sin.sin_addr.s_addr = INADDR_ANY;
505 } else {
506 switch (inet_pton(AF_INET, buf, &sin.sin_addr)) {
507 case 1:
508 break;
509 case 0:
510 fprintf(stderr, "rump_sp_tcp: cannot parse %s\n", buf);
511 return EINVAL;
512 case -1:
513 fprintf(stderr, "rump_sp_tcp: inet_pton failed\n");
514 return errno;
515 default:
516 assert(/*CONSTCOND*/0);
517 return EINVAL;
518 }
519 }
520
521 if (!allow_wildcard && sin.sin_addr.s_addr == INADDR_ANY) {
522 fprintf(stderr, "rump_sp_tcp: client needs !INADDR_ANY\n");
523 return EINVAL;
524 }
525
526 /* advance to port number & parse */
527 p++;
528 l = strspn(p, "0123456789");
529 if (l == 0) {
530 fprintf(stderr, "rump_sp_tcp: port now found: %s\n", p);
531 return EINVAL;
532 }
533 strncpy(buf, p, l);
534 buf[l] = '\0';
535
536 if (*(p+l) != '/' && *(p+l) != '\0') {
537 fprintf(stderr, "rump_sp_tcp: junk at end of port: %s\n", addr);
538 return EINVAL;
539 }
540
541 port = atoi(buf);
542 if (port < 0 || port >= (1<<(8*sizeof(in_port_t)))) {
543 fprintf(stderr, "rump_sp_tcp: port %d out of range\n", port);
544 return ERANGE;
545 }
546 sin.sin_port = htons(port);
547
548 *sa = malloc(sizeof(sin));
549 if (*sa == NULL)
550 return errno;
551 memcpy(*sa, &sin, sizeof(sin));
552 return 0;
553 }
554
555 static int
556 tcp_connecthook(int s)
557 {
558 int x;
559
560 x = 1;
561 setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &x, sizeof(x));
562
563 return 0;
564 }
565
566 /*ARGSUSED*/
567 static int
568 unix_parse(const char *addr, struct sockaddr **sa, int allow_wildcard)
569 {
570 struct sockaddr_un sun;
571 size_t slen;
572
573 if (strlen(addr) > sizeof(sun.sun_path))
574 return ENAMETOOLONG;
575
576 /*
577 * The pathname can be all kinds of spaghetti elementals,
578 * so meek and obidient we accept everything. However, use
579 * full path for easy cleanup in case someone gives a relative
580 * one and the server does a chdir() between now than the
581 * cleanup.
582 */
583 memset(&sun, 0, sizeof(sun));
584 sun.sun_family = AF_LOCAL;
585 if (*addr != '/') {
586 char mywd[PATH_MAX];
587
588 if (getcwd(mywd, sizeof(mywd)) == NULL) {
589 fprintf(stderr, "warning: cannot determine cwd, "
590 "omitting socket cleanup\n");
591 } else {
592 if (strlen(addr) + strlen(mywd) > sizeof(sun.sun_path))
593 return ENAMETOOLONG;
594 strlcpy(sun.sun_path, mywd, sizeof(sun.sun_path));
595 strlcat(sun.sun_path, "/", sizeof(sun.sun_path));
596 }
597 }
598 strlcat(sun.sun_path, addr, sizeof(sun.sun_path));
599 sun.sun_len = SUN_LEN(&sun);
600 slen = sun.sun_len+1; /* get the 0 too */
601
602 *sa = malloc(slen);
603 if (*sa == NULL)
604 return errno;
605 memcpy(*sa, &sun, slen);
606
607 return 0;
608 }
609
610 static void
611 unix_cleanup(struct sockaddr *sa)
612 {
613 struct sockaddr_un *sun = (void *)sa;
614
615 /*
616 * cleanup only absolute paths. see unix_parse() above
617 */
618 if (*sun->sun_path == '/') {
619 unlink(sun->sun_path);
620 }
621 }
622
623 /*ARGSUSED*/
624 static int
625 notsupp(void)
626 {
627
628 fprintf(stderr, "rump_sp: support not yet implemented\n");
629 return EOPNOTSUPP;
630 }
631
632 static int
633 success(void)
634 {
635
636 return 0;
637 }
638
639 struct {
640 const char *id;
641 int domain;
642 addrparse_fn ap;
643 connecthook_fn connhook;
644 cleanup_fn cleanup;
645 } parsetab[] = {
646 { "tcp", PF_INET, tcp_parse, tcp_connecthook, (cleanup_fn)success },
647 { "unix", PF_LOCAL, unix_parse, (connecthook_fn)success, unix_cleanup },
648 { "tcp6", PF_INET6, (addrparse_fn)notsupp, (connecthook_fn)success,
649 (cleanup_fn)success },
650 };
651 #define NPARSE (sizeof(parsetab)/sizeof(parsetab[0]))
652
653 static int
654 parseurl(const char *url, struct sockaddr **sap, unsigned *idxp,
655 int allow_wildcard)
656 {
657 char id[16];
658 const char *p, *p2;
659 size_t l;
660 unsigned i;
661 int error;
662
663 /*
664 * Parse the url
665 */
666
667 p = url;
668 p2 = strstr(p, "://");
669 if (!p2) {
670 fprintf(stderr, "rump_sp: invalid locator ``%s''\n", p);
671 return EINVAL;
672 }
673 l = p2-p;
674 if (l > sizeof(id)-1) {
675 fprintf(stderr, "rump_sp: identifier too long in ``%s''\n", p);
676 return EINVAL;
677 }
678
679 strncpy(id, p, l);
680 id[l] = '\0';
681 p2 += 3; /* beginning of address */
682
683 for (i = 0; i < NPARSE; i++) {
684 if (strcmp(id, parsetab[i].id) == 0) {
685 error = parsetab[i].ap(p2, sap, allow_wildcard);
686 if (error)
687 return error;
688 break;
689 }
690 }
691 if (i == NPARSE) {
692 fprintf(stderr, "rump_sp: invalid identifier ``%s''\n", p);
693 return EINVAL;
694 }
695
696 *idxp = i;
697 return 0;
698 }
699