#include <sys/types.h>
#include <sys/queue.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/endian.h>
#include <sys/uio.h>
#include <sys/soundcard.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <poll.h>
#include <sysexits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <net/if_vlan_var.h>
#include <net/bpf.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "int.h"
#define VOSS_HTTPD_BIND_MAX 8
#define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3)
struct http_state {
int fd;
uint64_t ts;
};
struct rtp_raw_packet {
struct {
uint32_t padding;
uint8_t dhost[6];
uint8_t shost[6];
uint16_t ether_type;
} __packed eth;
struct {
uint8_t hl_ver;
uint8_t tos;
uint16_t len;
uint16_t ident;
uint16_t offset;
uint8_t ttl;
uint8_t protocol;
uint16_t chksum;
union {
uint32_t sourceip;
uint16_t source16[2];
};
union {
uint32_t destip;
uint16_t dest16[2];
};
} __packed ip;
struct {
uint16_t srcport;
uint16_t dstport;
uint16_t len;
uint16_t chksum;
} __packed udp;
union {
uint8_t header8[12];
uint16_t header16[6];
uint32_t header32[3];
} __packed rtp;
} __packed;
static const char *
voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd)
{
const char *perr = NULL;
struct vlanreq vr = {};
struct ifreq ifr = {};
int fd;
fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (fd < 0) {
perr = "Cannot open raw RTP socket";
goto done;
}
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
ifr.ifr_data = (void *)&vr;
if (ioctl(fd, SIOCGETVLAN, &ifr) == 0)
pvc->profile->http.rtp_vlanid = vr.vlr_tag;
else
pvc->profile->http.rtp_vlanid = 0;
close(fd);
ifr.ifr_data = NULL;
*pfd = fd = open("/dev/bpf", O_RDWR);
if (fd < 0) {
perr = "Cannot open BPF device";
goto done;
}
if (ioctl(fd, BIOCSETIF, &ifr) != 0) {
perr = "Cannot bind BPF device to network interface";
goto done;
}
done:
if (perr != NULL && fd > -1)
close(fd);
return (perr);
}
static uint16_t
voss_ipv4_csum(const void *vptr, size_t count)
{
const uint16_t *ptr = vptr;
uint32_t sum = 0;
while (count--)
sum += *ptr++;
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (~sum);
}
static uint16_t
voss_udp_csum(uint32_t sum, const void *vhdr, size_t count,
const uint16_t *ptr, size_t length)
{
const uint16_t *hdr = vhdr;
while (count--)
sum += *hdr++;
while (length > 1) {
sum += *ptr++;
length -= 2;
}
if (length & 1)
sum += *__DECONST(uint8_t *, ptr);
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (~sum);
}
static void
voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
{
struct rtp_raw_packet pkt = {};
struct iovec iov[2];
size_t total_ip;
uint16_t port = atoi(pvc->profile->http.rtp_port);
size_t x;
memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost));
memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost));
pkt.eth.ether_type = htobe16(0x0800);
total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len;
iov[0].iov_base = pkt.eth.dhost;
iov[0].iov_len = 14 + total_ip - len;
iov[1].iov_base = alloca(len);
iov[1].iov_len = len;
for (x = 0; x != (len / 2); x++)
((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]);
pkt.ip.hl_ver = 0x45;
pkt.ip.len = htobe16(total_ip);
pkt.ip.ttl = 8;
pkt.ip.protocol = 17;
pkt.ip.sourceip = 0x01010101U;
pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0));
pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2);
pkt.udp.srcport = htobe16(port);
pkt.udp.dstport = htobe16(port);
pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip));
pkt.rtp.header8[0] = (2 << 6);
pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80;
pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum);
pkt.rtp.header32[1] = htobe32(ts);
pkt.rtp.header32[2] = htobe32(0);
pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] +
pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len,
(void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2,
iov[1].iov_base, iov[1].iov_len);
pvc->profile->http.rtp_seqnum++;
pvc->profile->http.rtp_ts += len / (2 * pvc->channels);
(void)writev(fd, iov, 2);
}
static void
voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
{
const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc);
const uint32_t max = 1420 - (1420 % mod);
while (len >= max) {
voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts);
len -= max;
ptr = (uint8_t *)ptr + max;
}
if (len != 0)
voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts);
}
static size_t
voss_httpd_usage(vclient_t *pvc)
{
size_t usage = 0;
size_t x;
for (x = 0; x < pvc->profile->http.nstate; x++)
usage += (pvc->profile->http.state[x].fd != -1);
return (usage);
}
static char *
voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen)
{
char buffer[2];
size_t size = 0;
if (fread(buffer, 1, 2, io) != 2)
return (NULL);
while (1) {
if (buffer[0] == '\r' && buffer[1] == '\n')
break;
if (size == (linelen - 1))
return (NULL);
linebuffer[size++] = buffer[0];
buffer[0] = buffer[1];
if (fread(buffer + 1, 1, 1, io) != 1)
return (NULL);
}
linebuffer[size++] = 0;
return (linebuffer);
}
static int
voss_http_generate_wav_header(vclient_t *pvc, FILE *io,
uintmax_t r_start, uintmax_t r_end, bool is_partial)
{
uint8_t buffer[256];
uint8_t *ptr;
uintmax_t dummy_len;
uintmax_t delta;
size_t mod;
size_t len;
size_t buflen;
ptr = buffer;
mod = pvc->channels * vclient_sample_bytes(pvc);
if (mod == 0 || sizeof(buffer) < (44 + mod - 1))
return (-1);
len = 44 + mod - 1;
len -= len % mod;
buflen = len;
memset(ptr, 0, len);
ptr[len - 8] = 'd';
ptr[len - 7] = 'a';
ptr[len - 6] = 't';
ptr[len - 5] = 'a';
ptr[len - 4] = 0x00;
ptr[len - 3] = 0xF0;
ptr[len - 2] = 0xFF;
ptr[len - 1] = 0x7F;
*ptr++ = 'R';
*ptr++ = 'I';
*ptr++ = 'F';
*ptr++ = 'F';
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 'W';
*ptr++ = 'A';
*ptr++ = 'V';
*ptr++ = 'E';
*ptr++ = 'f';
*ptr++ = 'm';
*ptr++ = 't';
*ptr++ = ' ';
len -= 28;
*ptr++ = len;
*ptr++ = len >> 8;
*ptr++ = len >> 16;
*ptr++ = len >> 24;
*ptr++ = 0x01;
*ptr++ = 0x00;
len = pvc->channels;
*ptr++ = len;
*ptr++ = len >> 8;
len = pvc->sample_rate;
*ptr++ = len;
*ptr++ = len >> 8;
*ptr++ = len >> 16;
*ptr++ = len >> 24;
len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
*ptr++ = len;
*ptr++ = len >> 8;
*ptr++ = len >> 16;
*ptr++ = len >> 24;
len = pvc->channels * vclient_sample_bytes(pvc);
*ptr++ = len;
*ptr++ = len >> 8;
len = vclient_sample_bytes(pvc) * 8;
*ptr++ = len;
*ptr++ = len >> 8;
if (r_start >= buflen && (r_start % mod) != 0)
return (2);
dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME;
if (r_end >= dummy_len)
r_end = dummy_len - 1;
delta = r_end - r_start + 1;
if (is_partial) {
fprintf(io, "HTTP/1.1 206 Partial Content\r\n"
"Content-Type: audio/wav\r\n"
"Server: virtual_oss/1.0\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"Connection: Close\r\n"
"Content-Range: bytes %ju-%ju/%ju\r\n"
"Content-Length: %ju\r\n"
"\r\n", r_start, r_end, dummy_len, delta);
} else {
fprintf(io, "HTTP/1.0 200 OK\r\n"
"Content-Type: audio/wav\r\n"
"Server: virtual_oss/1.0\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"Connection: Close\r\n"
"Content-Length: %ju\r\n"
"\r\n", dummy_len);
}
if (r_start < buflen) {
buflen -= r_start;
if (buflen > delta)
buflen = delta;
if (fwrite(buffer + r_start, buflen, 1, io) != 1)
return (-1);
if (buflen == delta)
return (1);
}
return (0);
}
static void
voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa)
{
char linebuffer[2048];
uintmax_t r_start = 0;
uintmax_t r_end = -1ULL;
bool is_partial = false;
char *line;
FILE *io;
size_t x;
int page;
io = fdopen(fd, "r+");
if (io == NULL)
goto done;
page = -1;
while (1) {
line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer));
if (line == NULL)
goto done;
if (line[0] == 0)
break;
if (page < 0 && (strstr(line, "GET / ") == line ||
strstr(line, "GET /index.html") == line)) {
page = 0;
} else if (page < 0 && strstr(line, "GET /stream.wav") == line) {
page = 1;
} else if (page < 0 && strstr(line, "GET /stream.m3u") == line) {
page = 2;
} else if (strstr(line, "Range: bytes=") == line &&
sscanf(line, "Range: bytes=%ju-%ju", &r_start, &r_end) >= 1) {
is_partial = true;
}
}
switch (page) {
case 0:
x = voss_httpd_usage(pvc);
fprintf(io, "HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"Server: virtual_oss/1.0\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"\r\n"
"<html><head><title>Welcome to live streaming</title>"
"<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />"
"<meta http-equiv=\"Pragma\" content=\"no-cache\" />"
"<meta http-equiv=\"Expires\" content=\"0\" />"
"</head>"
"<body>"
"<h1>Live HD stream</h1>"
"<br>"
"<br>"
"<h2>Alternative 1 (recommended)</h2>"
"<ol type=\"1\">"
"<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>"
"<li>Open VLC and select Network Stream</li>"
"<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>"
"</ol>"
"<br>"
"<br>"
"<h2>Alternative 2 (on your own)</h2>"
"<br>"
"<br>"
"<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>"
"<br>"
"<br>",
pvc->profile->http.host, pvc->profile->http.port,
pvc->profile->http.host, pvc->profile->http.port);
if (x == pvc->profile->http.nstate)
fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x);
else
fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x);
fprintf(io, "</body></html>");
break;
case 1:
for (x = 0; x < pvc->profile->http.nstate; x++) {
if (pvc->profile->http.state[x].fd >= 0)
continue;
switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) {
static const int enable = 1;
case 0:
fflush(io);
fdclose(io, NULL);
if (ioctl(fd, FIONBIO, &enable) != 0) {
close(fd);
return;
}
pvc->profile->http.state[x].ts =
virtual_oss_timestamp() - 1000000000ULL;
pvc->profile->http.state[x].fd = fd;
return;
case 1:
fclose(io);
return;
case 2:
fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n");
goto done;
default:
goto done;
}
}
fprintf(io, "HTTP/1.0 503 Out of Resources\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n");
break;
case 2:
fprintf(io, "HTTP/1.0 200 OK\r\n"
"Content-Type: audio/mpegurl\r\n"
"Server: virtual_oss/1.0\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"\r\n");
if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) {
fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port);
} else {
fprintf(io, "http://%s:%s/stream.wav\r\n",
pvc->profile->http.host, pvc->profile->http.port);
}
break;
default:
fprintf(io, "HTTP/1.0 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n"
"<html><head><title>virtual_oss</title></head>"
"<body>"
"<h1>Invalid page requested! "
"<a HREF=\"index.html\">Click here to go back</a>.</h1><br>"
"</body>"
"</html>");
break;
}
done:
if (io != NULL)
fclose(io);
else
close(fd);
}
static int
voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port,
struct pollfd *pfd, int num_sock, int buffer)
{
static const struct timeval timeout = {.tv_sec = 1};
struct addrinfo hints = {};
struct addrinfo *res;
struct addrinfo *res0;
int error;
int flag;
int s;
int ns = 0;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
if ((error = getaddrinfo(host, port, &hints, &res)))
return (-1);
res0 = res;
do {
if ((s = socket(res0->ai_family, res0->ai_socktype,
res0->ai_protocol)) < 0)
continue;
flag = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag));
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer));
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer));
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout));
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout));
if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) {
if (listen(s, pvc->profile->http.nstate) == 0) {
if (ns < num_sock) {
pfd[ns++].fd = s;
continue;
}
close(s);
break;
}
}
close(s);
} while ((res0 = res0->ai_next) != NULL);
freeaddrinfo(res);
return (ns);
}
static size_t
voss_httpd_buflimit(vclient_t *pvc)
{
return ((pvc->sample_rate / 4) *
pvc->channels * vclient_sample_bytes(pvc));
};
static void
voss_httpd_server(vclient_t *pvc)
{
const size_t bufferlimit = voss_httpd_buflimit(pvc);
const char *host = pvc->profile->http.host;
const char *port = pvc->profile->http.port;
struct sockaddr sa = {};
struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {};
int nfd;
nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit);
if (nfd < 1) {
errx(EX_SOFTWARE, "Could not bind to "
"'%s' and '%s'", host, port);
}
while (1) {
struct sockaddr_in si;
int ns = nfd;
int c;
int f;
for (c = 0; c != ns; c++) {
fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI |
POLLERR | POLLHUP | POLLNVAL);
fds[c].revents = 0;
}
if (poll(fds, ns, -1) < 0)
errx(EX_SOFTWARE, "Polling failed");
for (c = 0; c != ns; c++) {
socklen_t socklen = sizeof(sa);
if (fds[c].revents == 0)
continue;
f = accept(fds[c].fd, &sa, &socklen);
if (f < 0)
continue;
memcpy(&si, &sa, sizeof(sa));
voss_httpd_handle_connection(pvc, f, &si);
}
}
}
static void
voss_httpd_streamer(vclient_t *pvc)
{
const size_t bufferlimit = voss_httpd_buflimit(pvc);
uint8_t *ptr;
size_t len;
uint64_t ts;
size_t x;
atomic_lock();
while (1) {
if (vclient_export_read_locked(pvc) != 0) {
atomic_wait();
continue;
}
vring_get_read(&pvc->rx_ring[1], &ptr, &len);
if (len == 0) {
vring_reset(&pvc->rx_ring[1]);
atomic_wait();
continue;
}
atomic_unlock();
ts = virtual_oss_timestamp();
if (pvc->profile->http.rtp_fd > -1) {
voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd,
ptr, len, pvc->profile->http.rtp_ts);
}
for (x = 0; x < pvc->profile->http.nstate; x++) {
int fd = pvc->profile->http.state[x].fd;
uint64_t delta = ts - pvc->profile->http.state[x].ts;
uint8_t buf[1];
int write_len;
if (fd < 0) {
} else if (delta >= (8ULL * 1000000000ULL)) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if (ioctl(fd, FIONWRITE, &write_len) < 0) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) {
} else if (write(fd, ptr, len) != (ssize_t)len) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else {
pvc->profile->http.state[x].ts = ts;
}
}
atomic_lock();
vring_inc_read(&pvc->rx_ring[1], len);
}
}
const char *
voss_httpd_start(vprofile_t *pvp)
{
vclient_t *pvc;
pthread_t td;
int error;
size_t x;
if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0)
return (NULL);
pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate);
if (pvp->http.state == NULL)
return ("Could not allocate HTTP states");
for (x = 0; x != pvp->http.nstate; x++) {
pvp->http.state[x].fd = -1;
pvp->http.state[x].ts = 0;
}
pvc = vclient_alloc();
if (pvc == NULL)
return ("Could not allocate client for HTTP server");
pvc->profile = pvp;
if (pvp->http.rtp_ifname != NULL) {
const char *perr;
if (pvc->channels > 2)
return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth");
perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname,
&pvp->http.rtp_fd);
if (perr != NULL)
return (perr);
error = vclient_setup_buffers(pvc, 0, 0,
pvp->channels, AFMT_S16_LE, 44100);
} else {
pvp->http.rtp_fd = -1;
error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
vclient_get_default_fmt(pvp, VTYPE_WAV_HDR),
voss_dsp_sample_rate);
}
if (error != 0) {
vclient_free(pvc);
return ("Could not allocate buffers for HTTP server");
}
pvc->rx_enabled = 1;
pvc->type = VTYPE_OSS_DAT;
atomic_lock();
TAILQ_INSERT_TAIL(&pvp->head, pvc, entry);
atomic_unlock();
if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc))
return ("Could not create HTTP daemon thread");
if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc))
return ("Could not create HTTP streamer thread");
return (NULL);
}