1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2020 Hans Petter Selasky
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 AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE 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
21 * OR 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 #include <sys/types.h>
29 #include <sys/queue.h>
30 #include <sys/ioctl.h>
31 #include <sys/socket.h>
32 #include <sys/endian.h>
33 #include <sys/uio.h>
34 #include <sys/soundcard.h>
35
36 #include <stdio.h>
37 #include <stdint.h>
38 #include <stdbool.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <poll.h>
46 #include <sysexits.h>
47
48 #include <netdb.h>
49 #include <netinet/in.h>
50 #include <netinet/tcp.h>
51
52 #include <net/if.h>
53 #include <net/if_vlan_var.h>
54 #include <net/bpf.h>
55
56 #include <arpa/inet.h>
57
58 #include <pthread.h>
59
60 #include "int.h"
61
62 #define VOSS_HTTPD_BIND_MAX 8
63 #define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */
64
65 struct http_state {
66 int fd;
67 uint64_t ts;
68 };
69
70 struct rtp_raw_packet {
71 struct {
72 uint32_t padding;
73 uint8_t dhost[6];
74 uint8_t shost[6];
75 uint16_t ether_type;
76 } __packed eth;
77 struct {
78 uint8_t hl_ver;
79 uint8_t tos;
80 uint16_t len;
81 uint16_t ident;
82 uint16_t offset;
83 uint8_t ttl;
84 uint8_t protocol;
85 uint16_t chksum;
86 union {
87 uint32_t sourceip;
88 uint16_t source16[2];
89 };
90 union {
91 uint32_t destip;
92 uint16_t dest16[2];
93 };
94 } __packed ip;
95 struct {
96 uint16_t srcport;
97 uint16_t dstport;
98 uint16_t len;
99 uint16_t chksum;
100 } __packed udp;
101 union {
102 uint8_t header8[12];
103 uint16_t header16[6];
104 uint32_t header32[3];
105 } __packed rtp;
106
107 } __packed;
108
109 static const char *
voss_httpd_bind_rtp(vclient_t * pvc,const char * ifname,int * pfd)110 voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd)
111 {
112 const char *perr = NULL;
113 struct vlanreq vr = {};
114 struct ifreq ifr = {};
115 int fd;
116
117 fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
118 if (fd < 0) {
119 perr = "Cannot open raw RTP socket";
120 goto done;
121 }
122
123 strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
124 ifr.ifr_data = (void *)&vr;
125
126 if (ioctl(fd, SIOCGETVLAN, &ifr) == 0)
127 pvc->profile->http.rtp_vlanid = vr.vlr_tag;
128 else
129 pvc->profile->http.rtp_vlanid = 0;
130
131 close(fd);
132
133 ifr.ifr_data = NULL;
134
135 *pfd = fd = open("/dev/bpf", O_RDWR);
136 if (fd < 0) {
137 perr = "Cannot open BPF device";
138 goto done;
139 }
140
141 if (ioctl(fd, BIOCSETIF, &ifr) != 0) {
142 perr = "Cannot bind BPF device to network interface";
143 goto done;
144 }
145 done:
146 if (perr != NULL && fd > -1)
147 close(fd);
148 return (perr);
149 }
150
151 static uint16_t
voss_ipv4_csum(const void * vptr,size_t count)152 voss_ipv4_csum(const void *vptr, size_t count)
153 {
154 const uint16_t *ptr = vptr;
155 uint32_t sum = 0;
156
157 while (count--)
158 sum += *ptr++;
159
160 sum = (sum >> 16) + (sum & 0xffff);
161 sum += (sum >> 16);
162
163 return (~sum);
164 }
165
166 static uint16_t
voss_udp_csum(uint32_t sum,const void * vhdr,size_t count,const uint16_t * ptr,size_t length)167 voss_udp_csum(uint32_t sum, const void *vhdr, size_t count,
168 const uint16_t *ptr, size_t length)
169 {
170 const uint16_t *hdr = vhdr;
171
172 while (count--)
173 sum += *hdr++;
174
175 while (length > 1) {
176 sum += *ptr++;
177 length -= 2;
178 }
179
180 if (length & 1)
181 sum += *__DECONST(uint8_t *, ptr);
182
183 sum = (sum >> 16) + (sum & 0xffff);
184 sum += (sum >> 16);
185
186 return (~sum);
187 }
188
189 static void
voss_httpd_send_rtp_sub(vclient_t * pvc,int fd,void * ptr,size_t len,uint32_t ts)190 voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
191 {
192 struct rtp_raw_packet pkt = {};
193 struct iovec iov[2];
194 size_t total_ip;
195 uint16_t port = atoi(pvc->profile->http.rtp_port);
196 size_t x;
197
198 /* NOTE: BPF filter will insert VLAN header for us */
199 memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost));
200 memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost));
201 pkt.eth.ether_type = htobe16(0x0800);
202 total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len;
203
204 iov[0].iov_base = pkt.eth.dhost;
205 iov[0].iov_len = 14 + total_ip - len;
206
207 iov[1].iov_base = alloca(len);
208 iov[1].iov_len = len;
209
210 /* byte swap data - WAV files are 16-bit little endian */
211 for (x = 0; x != (len / 2); x++)
212 ((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]);
213
214 pkt.ip.hl_ver = 0x45;
215 pkt.ip.len = htobe16(total_ip);
216 pkt.ip.ttl = 8;
217 pkt.ip.protocol = 17; /* UDP */
218 pkt.ip.sourceip = 0x01010101U;
219 pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0));
220 pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2);
221
222 pkt.udp.srcport = htobe16(port);
223 pkt.udp.dstport = htobe16(port);
224 pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip));
225
226 pkt.rtp.header8[0] = (2 << 6);
227 pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80;
228
229 pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum);
230 pkt.rtp.header32[1] = htobe32(ts);
231 pkt.rtp.header32[2] = htobe32(0);
232
233 pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] +
234 pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len,
235 (void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2,
236 iov[1].iov_base, iov[1].iov_len);
237
238 pvc->profile->http.rtp_seqnum++;
239 pvc->profile->http.rtp_ts += len / (2 * pvc->channels);
240
241 (void)writev(fd, iov, 2);
242 }
243
244 static void
voss_httpd_send_rtp(vclient_t * pvc,int fd,void * ptr,size_t len,uint32_t ts)245 voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
246 {
247 const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc);
248 const uint32_t max = 1420 - (1420 % mod);
249
250 while (len >= max) {
251 voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts);
252 len -= max;
253 ptr = (uint8_t *)ptr + max;
254 }
255
256 if (len != 0)
257 voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts);
258 }
259
260 static size_t
voss_httpd_usage(vclient_t * pvc)261 voss_httpd_usage(vclient_t *pvc)
262 {
263 size_t usage = 0;
264 size_t x;
265
266 for (x = 0; x < pvc->profile->http.nstate; x++)
267 usage += (pvc->profile->http.state[x].fd != -1);
268 return (usage);
269 }
270
271 static char *
voss_httpd_read_line(FILE * io,char * linebuffer,size_t linelen)272 voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen)
273 {
274 char buffer[2];
275 size_t size = 0;
276
277 if (fread(buffer, 1, 2, io) != 2)
278 return (NULL);
279
280 while (1) {
281 if (buffer[0] == '\r' && buffer[1] == '\n')
282 break;
283 if (size == (linelen - 1))
284 return (NULL);
285 linebuffer[size++] = buffer[0];
286 buffer[0] = buffer[1];
287 if (fread(buffer + 1, 1, 1, io) != 1)
288 return (NULL);
289 }
290 linebuffer[size++] = 0;
291
292 return (linebuffer);
293 }
294
295 static int
voss_http_generate_wav_header(vclient_t * pvc,FILE * io,uintmax_t r_start,uintmax_t r_end,bool is_partial)296 voss_http_generate_wav_header(vclient_t *pvc, FILE *io,
297 uintmax_t r_start, uintmax_t r_end, bool is_partial)
298 {
299 uint8_t buffer[256];
300 uint8_t *ptr;
301 uintmax_t dummy_len;
302 uintmax_t delta;
303 size_t mod;
304 size_t len;
305 size_t buflen;
306
307 ptr = buffer;
308 mod = pvc->channels * vclient_sample_bytes(pvc);
309
310 if (mod == 0 || sizeof(buffer) < (44 + mod - 1))
311 return (-1);
312
313 /* align to next sample */
314 len = 44 + mod - 1;
315 len -= len % mod;
316
317 buflen = len;
318
319 /* clear block */
320 memset(ptr, 0, len);
321
322 /* fill out data header */
323 ptr[len - 8] = 'd';
324 ptr[len - 7] = 'a';
325 ptr[len - 6] = 't';
326 ptr[len - 5] = 'a';
327
328 /* magic for unspecified length */
329 ptr[len - 4] = 0x00;
330 ptr[len - 3] = 0xF0;
331 ptr[len - 2] = 0xFF;
332 ptr[len - 1] = 0x7F;
333
334 /* fill out header */
335 *ptr++ = 'R';
336 *ptr++ = 'I';
337 *ptr++ = 'F';
338 *ptr++ = 'F';
339
340 /* total chunk size - unknown */
341
342 *ptr++ = 0;
343 *ptr++ = 0;
344 *ptr++ = 0;
345 *ptr++ = 0;
346
347 *ptr++ = 'W';
348 *ptr++ = 'A';
349 *ptr++ = 'V';
350 *ptr++ = 'E';
351 *ptr++ = 'f';
352 *ptr++ = 'm';
353 *ptr++ = 't';
354 *ptr++ = ' ';
355
356 /* make sure header fits in PCM block */
357 len -= 28;
358
359 *ptr++ = len;
360 *ptr++ = len >> 8;
361 *ptr++ = len >> 16;
362 *ptr++ = len >> 24;
363
364 /* audioformat = PCM */
365
366 *ptr++ = 0x01;
367 *ptr++ = 0x00;
368
369 /* number of channels */
370
371 len = pvc->channels;
372
373 *ptr++ = len;
374 *ptr++ = len >> 8;
375
376 /* sample rate */
377
378 len = pvc->sample_rate;
379
380 *ptr++ = len;
381 *ptr++ = len >> 8;
382 *ptr++ = len >> 16;
383 *ptr++ = len >> 24;
384
385 /* byte rate */
386
387 len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
388
389 *ptr++ = len;
390 *ptr++ = len >> 8;
391 *ptr++ = len >> 16;
392 *ptr++ = len >> 24;
393
394 /* block align */
395
396 len = pvc->channels * vclient_sample_bytes(pvc);
397
398 *ptr++ = len;
399 *ptr++ = len >> 8;
400
401 /* bits per sample */
402
403 len = vclient_sample_bytes(pvc) * 8;
404
405 *ptr++ = len;
406 *ptr++ = len >> 8;
407
408 /* check if alignment is correct */
409 if (r_start >= buflen && (r_start % mod) != 0)
410 return (2);
411
412 dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
413 dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME;
414
415 /* fixup end */
416 if (r_end >= dummy_len)
417 r_end = dummy_len - 1;
418
419 delta = r_end - r_start + 1;
420
421 if (is_partial) {
422 fprintf(io, "HTTP/1.1 206 Partial Content\r\n"
423 "Content-Type: audio/wav\r\n"
424 "Server: virtual_oss/1.0\r\n"
425 "Cache-Control: no-cache, no-store\r\n"
426 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
427 "Connection: Close\r\n"
428 "Content-Range: bytes %ju-%ju/%ju\r\n"
429 "Content-Length: %ju\r\n"
430 "\r\n", r_start, r_end, dummy_len, delta);
431 } else {
432 fprintf(io, "HTTP/1.0 200 OK\r\n"
433 "Content-Type: audio/wav\r\n"
434 "Server: virtual_oss/1.0\r\n"
435 "Cache-Control: no-cache, no-store\r\n"
436 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
437 "Connection: Close\r\n"
438 "Content-Length: %ju\r\n"
439 "\r\n", dummy_len);
440 }
441
442 /* check if we should insert a header */
443 if (r_start < buflen) {
444 buflen -= r_start;
445 if (buflen > delta)
446 buflen = delta;
447 /* send data */
448 if (fwrite(buffer + r_start, buflen, 1, io) != 1)
449 return (-1);
450 /* check if all data was read */
451 if (buflen == delta)
452 return (1);
453 }
454 return (0);
455 }
456
457 static void
voss_httpd_handle_connection(vclient_t * pvc,int fd,const struct sockaddr_in * sa)458 voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa)
459 {
460 char linebuffer[2048];
461 uintmax_t r_start = 0;
462 uintmax_t r_end = -1ULL;
463 bool is_partial = false;
464 char *line;
465 FILE *io;
466 size_t x;
467 int page;
468
469 io = fdopen(fd, "r+");
470 if (io == NULL)
471 goto done;
472
473 page = -1;
474
475 /* dump HTTP request header */
476 while (1) {
477 line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer));
478 if (line == NULL)
479 goto done;
480 if (line[0] == 0)
481 break;
482 if (page < 0 && (strstr(line, "GET / ") == line ||
483 strstr(line, "GET /index.html") == line)) {
484 page = 0;
485 } else if (page < 0 && strstr(line, "GET /stream.wav") == line) {
486 page = 1;
487 } else if (page < 0 && strstr(line, "GET /stream.m3u") == line) {
488 page = 2;
489 } else if (strstr(line, "Range: bytes=") == line &&
490 sscanf(line, "Range: bytes=%ju-%ju", &r_start, &r_end) >= 1) {
491 is_partial = true;
492 }
493 }
494
495 switch (page) {
496 case 0:
497 x = voss_httpd_usage(pvc);
498
499 fprintf(io, "HTTP/1.0 200 OK\r\n"
500 "Content-Type: text/html\r\n"
501 "Server: virtual_oss/1.0\r\n"
502 "Cache-Control: no-cache, no-store\r\n"
503 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
504 "\r\n"
505 "<html><head><title>Welcome to live streaming</title>"
506 "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />"
507 "<meta http-equiv=\"Pragma\" content=\"no-cache\" />"
508 "<meta http-equiv=\"Expires\" content=\"0\" />"
509 "</head>"
510 "<body>"
511 "<h1>Live HD stream</h1>"
512 "<br>"
513 "<br>"
514 "<h2>Alternative 1 (recommended)</h2>"
515 "<ol type=\"1\">"
516 "<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>"
517 "<li>Open VLC and select Network Stream</li>"
518 "<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>"
519 "</ol>"
520 "<br>"
521 "<br>"
522 "<h2>Alternative 2 (on your own)</h2>"
523 "<br>"
524 "<br>"
525 "<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>"
526 "<br>"
527 "<br>",
528 pvc->profile->http.host, pvc->profile->http.port,
529 pvc->profile->http.host, pvc->profile->http.port);
530
531 if (x == pvc->profile->http.nstate)
532 fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x);
533 else
534 fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x);
535
536 fprintf(io, "</body></html>");
537 break;
538 case 1:
539 for (x = 0; x < pvc->profile->http.nstate; x++) {
540 if (pvc->profile->http.state[x].fd >= 0)
541 continue;
542 switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) {
543 static const int enable = 1;
544
545 case 0:
546 fflush(io);
547 fdclose(io, NULL);
548 if (ioctl(fd, FIONBIO, &enable) != 0) {
549 close(fd);
550 return;
551 }
552 pvc->profile->http.state[x].ts =
553 virtual_oss_timestamp() - 1000000000ULL;
554 pvc->profile->http.state[x].fd = fd;
555 return;
556 case 1:
557 fclose(io);
558 return;
559 case 2:
560 fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n"
561 "Server: virtual_oss/1.0\r\n"
562 "\r\n");
563 goto done;
564 default:
565 goto done;
566 }
567 }
568 fprintf(io, "HTTP/1.0 503 Out of Resources\r\n"
569 "Server: virtual_oss/1.0\r\n"
570 "\r\n");
571 break;
572 case 2:
573 fprintf(io, "HTTP/1.0 200 OK\r\n"
574 "Content-Type: audio/mpegurl\r\n"
575 "Server: virtual_oss/1.0\r\n"
576 "Cache-Control: no-cache, no-store\r\n"
577 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
578 "\r\n");
579 if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) {
580 fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port);
581 } else {
582 fprintf(io, "http://%s:%s/stream.wav\r\n",
583 pvc->profile->http.host, pvc->profile->http.port);
584 }
585 break;
586 default:
587 fprintf(io, "HTTP/1.0 404 Not Found\r\n"
588 "Content-Type: text/html\r\n"
589 "Server: virtual_oss/1.0\r\n"
590 "\r\n"
591 "<html><head><title>virtual_oss</title></head>"
592 "<body>"
593 "<h1>Invalid page requested! "
594 "<a HREF=\"index.html\">Click here to go back</a>.</h1><br>"
595 "</body>"
596 "</html>");
597 break;
598 }
599 done:
600 if (io != NULL)
601 fclose(io);
602 else
603 close(fd);
604 }
605
606 static int
voss_httpd_do_listen(vclient_t * pvc,const char * host,const char * port,struct pollfd * pfd,int num_sock,int buffer)607 voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port,
608 struct pollfd *pfd, int num_sock, int buffer)
609 {
610 static const struct timeval timeout = {.tv_sec = 1};
611 struct addrinfo hints = {};
612 struct addrinfo *res;
613 struct addrinfo *res0;
614 int error;
615 int flag;
616 int s;
617 int ns = 0;
618
619 hints.ai_family = AF_UNSPEC;
620 hints.ai_socktype = SOCK_STREAM;
621 hints.ai_protocol = IPPROTO_TCP;
622 hints.ai_flags = AI_PASSIVE;
623
624 if ((error = getaddrinfo(host, port, &hints, &res)))
625 return (-1);
626
627 res0 = res;
628
629 do {
630 if ((s = socket(res0->ai_family, res0->ai_socktype,
631 res0->ai_protocol)) < 0)
632 continue;
633
634 flag = 1;
635 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag));
636 setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer));
637 setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer));
638 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout));
639 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout));
640
641 if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) {
642 if (listen(s, pvc->profile->http.nstate) == 0) {
643 if (ns < num_sock) {
644 pfd[ns++].fd = s;
645 continue;
646 }
647 close(s);
648 break;
649 }
650 }
651 close(s);
652 } while ((res0 = res0->ai_next) != NULL);
653
654 freeaddrinfo(res);
655
656 return (ns);
657 }
658
659 static size_t
voss_httpd_buflimit(vclient_t * pvc)660 voss_httpd_buflimit(vclient_t *pvc)
661 {
662 /* don't buffer more than 250ms */
663 return ((pvc->sample_rate / 4) *
664 pvc->channels * vclient_sample_bytes(pvc));
665 };
666
667 static void
voss_httpd_server(vclient_t * pvc)668 voss_httpd_server(vclient_t *pvc)
669 {
670 const size_t bufferlimit = voss_httpd_buflimit(pvc);
671 const char *host = pvc->profile->http.host;
672 const char *port = pvc->profile->http.port;
673 struct sockaddr sa = {};
674 struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {};
675 int nfd;
676
677 nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit);
678 if (nfd < 1) {
679 errx(EX_SOFTWARE, "Could not bind to "
680 "'%s' and '%s'", host, port);
681 }
682
683 while (1) {
684 struct sockaddr_in si;
685 int ns = nfd;
686 int c;
687 int f;
688
689 for (c = 0; c != ns; c++) {
690 fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI |
691 POLLERR | POLLHUP | POLLNVAL);
692 fds[c].revents = 0;
693 }
694 if (poll(fds, ns, -1) < 0)
695 errx(EX_SOFTWARE, "Polling failed");
696
697 for (c = 0; c != ns; c++) {
698 socklen_t socklen = sizeof(sa);
699
700 if (fds[c].revents == 0)
701 continue;
702 f = accept(fds[c].fd, &sa, &socklen);
703 if (f < 0)
704 continue;
705 memcpy(&si, &sa, sizeof(sa));
706 voss_httpd_handle_connection(pvc, f, &si);
707 }
708 }
709 }
710
711 static void
voss_httpd_streamer(vclient_t * pvc)712 voss_httpd_streamer(vclient_t *pvc)
713 {
714 const size_t bufferlimit = voss_httpd_buflimit(pvc);
715 uint8_t *ptr;
716 size_t len;
717 uint64_t ts;
718 size_t x;
719
720 atomic_lock();
721 while (1) {
722 if (vclient_export_read_locked(pvc) != 0) {
723 atomic_wait();
724 continue;
725 }
726 vring_get_read(&pvc->rx_ring[1], &ptr, &len);
727 if (len == 0) {
728 /* try to avoid ring wraps */
729 vring_reset(&pvc->rx_ring[1]);
730 atomic_wait();
731 continue;
732 }
733 atomic_unlock();
734
735 ts = virtual_oss_timestamp();
736
737 /* check if we should send RTP data, if any */
738 if (pvc->profile->http.rtp_fd > -1) {
739 voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd,
740 ptr, len, pvc->profile->http.rtp_ts);
741 }
742
743 /* send HTTP data, if any */
744 for (x = 0; x < pvc->profile->http.nstate; x++) {
745 int fd = pvc->profile->http.state[x].fd;
746 uint64_t delta = ts - pvc->profile->http.state[x].ts;
747 uint8_t buf[1];
748 int write_len;
749
750 if (fd < 0) {
751 /* do nothing */
752 } else if (delta >= (8ULL * 1000000000ULL)) {
753 /* no data for 8 seconds - terminate */
754 pvc->profile->http.state[x].fd = -1;
755 close(fd);
756 } else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) {
757 pvc->profile->http.state[x].fd = -1;
758 close(fd);
759 } else if (ioctl(fd, FIONWRITE, &write_len) < 0) {
760 pvc->profile->http.state[x].fd = -1;
761 close(fd);
762 } else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) {
763 /* do nothing */
764 } else if (write(fd, ptr, len) != (ssize_t)len) {
765 pvc->profile->http.state[x].fd = -1;
766 close(fd);
767 } else {
768 /* update timestamp */
769 pvc->profile->http.state[x].ts = ts;
770 }
771 }
772
773 atomic_lock();
774 vring_inc_read(&pvc->rx_ring[1], len);
775 }
776 }
777
778 const char *
voss_httpd_start(vprofile_t * pvp)779 voss_httpd_start(vprofile_t *pvp)
780 {
781 vclient_t *pvc;
782 pthread_t td;
783 int error;
784 size_t x;
785
786 if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0)
787 return (NULL);
788
789 pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate);
790 if (pvp->http.state == NULL)
791 return ("Could not allocate HTTP states");
792
793 for (x = 0; x != pvp->http.nstate; x++) {
794 pvp->http.state[x].fd = -1;
795 pvp->http.state[x].ts = 0;
796 }
797
798 pvc = vclient_alloc();
799 if (pvc == NULL)
800 return ("Could not allocate client for HTTP server");
801
802 pvc->profile = pvp;
803
804 if (pvp->http.rtp_ifname != NULL) {
805 const char *perr;
806
807 if (pvc->channels > 2)
808 return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth");
809
810 /* bind to UDP port */
811 perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname,
812 &pvp->http.rtp_fd);
813 if (perr != NULL)
814 return (perr);
815
816 /* setup buffers */
817 error = vclient_setup_buffers(pvc, 0, 0,
818 pvp->channels, AFMT_S16_LE, 44100);
819 } else {
820 pvp->http.rtp_fd = -1;
821
822 /* setup buffers */
823 error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
824 vclient_get_default_fmt(pvp, VTYPE_WAV_HDR),
825 voss_dsp_sample_rate);
826 }
827
828 if (error != 0) {
829 vclient_free(pvc);
830 return ("Could not allocate buffers for HTTP server");
831 }
832
833 /* trigger enabled */
834 pvc->rx_enabled = 1;
835
836 pvc->type = VTYPE_OSS_DAT;
837
838 atomic_lock();
839 TAILQ_INSERT_TAIL(&pvp->head, pvc, entry);
840 atomic_unlock();
841
842 if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc))
843 return ("Could not create HTTP daemon thread");
844 if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc))
845 return ("Could not create HTTP streamer thread");
846
847 return (NULL);
848 }
849