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