xref: /src/usr.sbin/virtual_oss/virtual_oss/httpd.c (revision 6d5a428056b52c7ce47b01d6af8aaaff6feecfdd)
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