mirror of
https://github.com/DigitalDevices/octonet.git
synced 2023-10-10 13:36:52 +02:00
2951 lines
70 KiB
C
2951 lines
70 KiB
C
/*
|
|
(C) 2012-15 Digital Devices GmbH.
|
|
|
|
This file is part of the octoserve SAT>IP server.
|
|
|
|
Octoserve is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Octoserve is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with octoserve. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "octoserve.h"
|
|
|
|
#include <getopt.h>
|
|
|
|
uint32_t debug;
|
|
uint32_t flags;
|
|
uint32_t conform = 0;
|
|
#define FLAGS_TRANSPORT 1
|
|
|
|
static uint32_t session_is_playing(struct ossess *sess);
|
|
|
|
uint64_t mtime_nano(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts))
|
|
return 0;
|
|
return ts.tv_sec * 1000000000 + ts.tv_nsec;
|
|
}
|
|
|
|
void dump(const uint8_t *b, int l)
|
|
{
|
|
int i, j;
|
|
|
|
for (j = 0; j < l; j += 16, b += 16) {
|
|
for (i = 0; i < 16; i++)
|
|
if (i + j < l)
|
|
fprintf(stderr, "%02x ", b[i]);
|
|
else
|
|
fprintf(stderr, " ");
|
|
fprintf(stderr, " | ");
|
|
for (i = 0; i < 16; i++)
|
|
if (i + j < l)
|
|
fputc((b[i] > 31 && b[i] < 127) ? b[i] : '.', stderr);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
static void update_switch_vec(struct ossess *sess)
|
|
{
|
|
struct osmcc *mcc;
|
|
uint32_t vec = 0;
|
|
struct ossess *ownsess = sess->stream->session;
|
|
|
|
if (!sess->os->has_switch)
|
|
return;
|
|
for (mcc = sess->mccs.lh_first; mcc; mcc = mcc->mcc.le_next)
|
|
vec |= mcc->port_vec;
|
|
//if (conform && ownsess->trans.mcast && (ownsess->playing & 2))
|
|
if (conform && ownsess->trans.mcast && session_is_playing(ownsess))
|
|
vec |= 0x1f;
|
|
if (vec != sess->mcc_port_vec_set || sess->port_vec != sess->port_vec_set) {
|
|
sess->mcc_port_vec_set = vec;
|
|
sess->port_vec_set = sess->port_vec;
|
|
switch_set_multicast(sess->trans.mcmac,
|
|
sess->mcc_port_vec_set | sess->port_vec_set);
|
|
}
|
|
}
|
|
|
|
struct rtsp_error rtsp_errors[] = {
|
|
{100, "Continue"},
|
|
{200, "OK"},
|
|
{400, "Bad Request"},
|
|
{403, "Forbidden"},
|
|
{404, "Not Found"},
|
|
{405, "Method Not Allowed"},
|
|
{406, "Not Acceptable"},
|
|
{408, "Request Timeout"},
|
|
{414, "Request-URI Too Long"},
|
|
{453, "Not Enough Bandwidth"},
|
|
{454, "Session Not Found"},
|
|
{455, "Method Not Valid in This State"},
|
|
{461, "Unsupported Transport"},
|
|
{500, "Internal Server Error"},
|
|
{501, "Not Implemented"},
|
|
{503, "Service Unavailable"},
|
|
{505, "Version Not Supported"},
|
|
{551, "Option Not Supported"},
|
|
{0, ""},
|
|
};
|
|
|
|
static int adrtoip(struct sockaddr *sa, uint8_t *ip)
|
|
{
|
|
unsigned short af = sa->sa_family;
|
|
void *adr;
|
|
|
|
if (af == AF_INET)
|
|
adr = &((struct sockaddr_in *) sa)->sin_addr;
|
|
else if (af == AF_INET6)
|
|
adr = &((struct sockaddr_in6 *) sa)->sin6_addr;
|
|
else
|
|
return -1;
|
|
memcpy(ip, adr, af == AF_INET ? 4 : 16);
|
|
return 0;
|
|
}
|
|
|
|
static int check_self(struct sockaddr *sadr, uint8_t *mac)
|
|
{
|
|
uint8_t ip[16];
|
|
|
|
if (adrtoip(sadr, ip) < 0)
|
|
return -1;
|
|
}
|
|
|
|
static int get_mac(char *ifname, struct sockaddr *sadr, uint8_t *mac)
|
|
{
|
|
struct arpreq arpreq;
|
|
int s;
|
|
|
|
if (sadr->sa_family == AF_INET)
|
|
get_route(&((struct sockaddr_in *) sadr)->sin_addr);
|
|
|
|
s = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (s < 0)
|
|
return -1;
|
|
memset(&arpreq, 0, sizeof(struct arpreq));
|
|
memcpy(&arpreq.arp_pa, sadr, sizeof(struct sockaddr));
|
|
|
|
arpreq.arp_ha.sa_family = ARPHRD_ETHER;
|
|
strcpy(arpreq.arp_dev, ifname);
|
|
if (ioctl(s, SIOCGARP, &arpreq) < 0) {
|
|
perror("get_mac socket error on");
|
|
dump((uint8_t *)sadr, sizeof(struct sockaddr));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
close(s);
|
|
memcpy(mac, arpreq.arp_ha.sa_data, 6);
|
|
dbgprintf(DEBUG_NET, "%s=%02x:%02x:%02x:%02x:%02x:%02x\n", ifname,
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
return 0;
|
|
}
|
|
|
|
static int ip2mac(uint8_t *ip, uint8_t *mac)
|
|
{
|
|
struct arpreq arpreq;
|
|
struct sockaddr_in *sin = (struct sockaddr_in *) &arpreq.arp_pa;
|
|
int s;
|
|
|
|
s = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (s < 0)
|
|
return -1;
|
|
memset(&arpreq, 0, sizeof(struct arpreq));
|
|
sin->sin_family = AF_INET;
|
|
memcpy(&sin->sin_addr.s_addr, ip, 4);
|
|
|
|
if (ioctl(s, SIOCGARP, &arpreq) < 0) {
|
|
perror("ip2mac socket error");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
close(s);
|
|
memcpy(mac, arpreq.arp_ha.sa_data, 6);
|
|
dbgprintf(DEBUG_NET, "%u.%u.%u.%u=%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
ip[0], ip[1], ip[2], ip[3],
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
return 0;
|
|
}
|
|
|
|
static void add_fd(int fd, int *mfd, fd_set *fds)
|
|
{
|
|
FD_SET(fd, fds);
|
|
if (fd > *mfd)
|
|
*mfd = fd;
|
|
}
|
|
|
|
int readmii(int id, int reg)
|
|
{
|
|
int fd;
|
|
struct ifreq ifr;
|
|
struct mii_ioctl_data *mii = (struct mii_ioctl_data *) &ifr.ifr_data;
|
|
|
|
strcpy(ifr.ifr_name, "eth0");
|
|
mii->phy_id = id;
|
|
mii->reg_num = reg;
|
|
mii->val_in = 0;
|
|
mii->val_out = 0;
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
ioctl(fd, SIOCGMIIREG, &ifr);
|
|
dbgprintf(DEBUG_NET, "mii %02x.%02x = %02x\n", id, reg, mii->val_out);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
void send_error(struct oscon *con, int err)
|
|
{
|
|
uint8_t buf[256];
|
|
int len;
|
|
char *err_str;
|
|
struct rtsp_error *rerr;
|
|
|
|
for (rerr = rtsp_errors; rerr->number; rerr++)
|
|
if (rerr->number == err)
|
|
break;
|
|
if (!rerr->number) {
|
|
dbgprintf(DEBUG_NET, "Internal Error: invalid error number %d\n", err);
|
|
return;
|
|
}
|
|
len=sprintf(buf,
|
|
"RTSP/1.0 %d %s\r\n"
|
|
"CSeq: %d\r\n"
|
|
"\r\n", err, rerr->name, con->seq);
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send Error:\n%s\n", buf);
|
|
}
|
|
|
|
static void release_session(struct ossess *oss);
|
|
|
|
static void release_ca(struct dvbca *ca)
|
|
{
|
|
dbgprintf(DEBUG_SYS, "release ca %d\n", ca->nr);
|
|
pthread_mutex_lock(&ca->os->lock);
|
|
ca->stream->ca = NULL;
|
|
ca->state = 0;
|
|
pthread_mutex_unlock(&ca->os->lock);
|
|
}
|
|
|
|
static struct dvbca *alloc_ca_num(struct osstrm *str, int num)
|
|
{
|
|
struct octoserve *os = str->os;
|
|
struct dvbca *ca;
|
|
|
|
if (num >= os->dvbca_num)
|
|
return NULL;
|
|
pthread_mutex_lock(&os->lock);
|
|
ca = &os->dvbca[num];
|
|
if (ca->state == 0) {
|
|
dbgprintf(DEBUG_CI, "alloced ca %d\n", num);
|
|
pthread_mutex_lock(&ca->mutex);
|
|
memset(ca->pmt, 0, sizeof(ca->pmt));
|
|
ca->state = 1;
|
|
ca->stream = str;
|
|
ca->input = str->fe->nr - 1;
|
|
pthread_mutex_unlock(&ca->mutex);
|
|
pthread_mutex_unlock(&os->lock);
|
|
return ca;
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static struct dvbca *alloc_ca(struct osstrm *str)
|
|
{
|
|
struct octoserve *os = str->os;
|
|
struct dvbca *ca;
|
|
uint32_t i;
|
|
|
|
dbgprintf(DEBUG_CI, "alloc ca\n");
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < os->dvbca_num; i++) {
|
|
ca = &os->dvbca[i];
|
|
if (ca->state == 0) {
|
|
dbgprintf(DEBUG_CI, "alloced ca %d\n", i);
|
|
pthread_mutex_lock(&ca->mutex);
|
|
memset(ca->pmt, 0, sizeof(ca->pmt));
|
|
ca->state = 1;
|
|
ca->stream = str;
|
|
ca->input = str->fe->nr - 1;
|
|
pthread_mutex_unlock(&ca->mutex);
|
|
pthread_mutex_unlock(&os->lock);
|
|
return ca;
|
|
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static void release_fe(struct octoserve *os, struct dvbfe *fe)
|
|
{
|
|
if (!fe)
|
|
return;
|
|
dbgprintf(DEBUG_SYS, "release fe %d\n", fe->nr);
|
|
fe->state = 2;
|
|
pthread_join(fe->pt, NULL);
|
|
}
|
|
|
|
static struct dvbfe *alloc_fe_num(struct octoserve *os, int i, int type)
|
|
{
|
|
struct dvbfe *fe;
|
|
|
|
if (i > os->dvbfe_num)
|
|
return NULL;
|
|
dbgprintf(DEBUG_SYS, "alloc_fe_num %d\n", i);
|
|
pthread_mutex_lock(&os->lock);
|
|
fe = &os->dvbfe[i];
|
|
if (fe->state || !(fe->type & (1UL << type))) {
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
fe->n_tune = 0;
|
|
fe->state = 1;
|
|
pthread_create(&fe->pt, NULL, (void *) handle_fe, fe);
|
|
pthread_mutex_unlock(&os->lock);
|
|
dbgprintf(DEBUG_SYS, "Allocated fe %d = %d/%d, fd=%d\n",
|
|
fe->nr, fe->anum, fe->fnum, fe->fd);
|
|
return fe;
|
|
}
|
|
|
|
static struct dvbfe *alloc_fe(struct octoserve *os, int type)
|
|
{
|
|
int i;
|
|
struct dvbfe *fe;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < os->dvbfe_num; i++) {
|
|
fe = &os->dvbfe[i];
|
|
if (fe->state == 0 &&
|
|
(fe->type & (1UL << type))) {
|
|
pthread_mutex_unlock(&os->lock);
|
|
return alloc_fe_num(os, i, type);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static void release_con(struct oscon *con)
|
|
{
|
|
close(con->sock);
|
|
con->state = 0;
|
|
dbgprintf(DEBUG_SYS, "releasing con %d\n", con->nr);
|
|
}
|
|
|
|
static void shutdown_con(struct oscon *con)
|
|
{
|
|
con->session = 0;
|
|
con->state = 3;
|
|
con->timeout = mtime(NULL) + 10;
|
|
dbgprintf(DEBUG_SYS, "shutdown con %u at %u\n", con->nr, con->timeout);
|
|
}
|
|
|
|
static void _release_session(struct ossess *oss)
|
|
{
|
|
struct octoserve *os = oss->os;
|
|
int i;
|
|
|
|
dbgprintf(DEBUG_SYS, "releasing session %d\n", oss->nr);
|
|
for (i = 0; i < MAX_CONNECT; i++) {
|
|
if (os->con[i].state &&
|
|
(os->con[i].session == oss))
|
|
shutdown_con(&os->con[i]);
|
|
}
|
|
if (oss->nsfd >= 0) {
|
|
dbgprintf(DEBUG_SYS, "closing netstream of session %d\n", oss->nr);
|
|
close(oss->nsfd);
|
|
}
|
|
oss->playing = 0;
|
|
oss->state = 0;
|
|
if (oss->trans.mcast && conform) {
|
|
dbgprintf(DEBUG_SYS, "check mc owner\n");
|
|
if (!session_is_playing(oss))
|
|
ioctl(oss->stream->session->nsfd, NS_STOP);
|
|
}
|
|
}
|
|
|
|
static void release_stream(struct osstrm *str)
|
|
{
|
|
struct octoserve *os = str->os;
|
|
int i;
|
|
dbgprintf(DEBUG_SYS, "release stream %d\n", str->nr);
|
|
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
if (os->session[i].state &&
|
|
(os->session[i].stream == str))
|
|
_release_session(&os->session[i]);
|
|
}
|
|
str->state = 0;
|
|
release_fe(os, str->fe);
|
|
if (str->ca)
|
|
release_ca(str->ca);
|
|
}
|
|
|
|
static struct osstrm *alloc_stream(struct octoserve *os)
|
|
{
|
|
int i;
|
|
struct osstrm *str;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_STREAM; i++) {
|
|
str = &os->stream[i];
|
|
if (str->state == 0) {
|
|
memset(str, 0, sizeof(struct osstrm));
|
|
str->os = os;
|
|
str->nr = i + 1;
|
|
str->state = 1;
|
|
str->sport = 8000 + 2 * i;
|
|
str->sport2 = 8000 + 2 * i + 1;
|
|
pthread_mutex_unlock(&os->lock);
|
|
dbgprintf(DEBUG_SYS, "Allocated stream %d\n", str->nr);
|
|
return str;}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static struct osstrm *get_stream(struct octoserve *os, int id)
|
|
{
|
|
int i;
|
|
struct osstrm *str, *r = NULL;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_STREAM; i++) {
|
|
str = &os->stream[i];
|
|
if (str->state && (str->nr == id)) {
|
|
r = str;
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return r;
|
|
}
|
|
|
|
static void release_session(struct ossess *oss)
|
|
{
|
|
struct octoserve *os = oss->os;
|
|
int i;
|
|
|
|
dbgprintf(DEBUG_SYS, "release session nr %d id %010d\n", oss->nr, oss->id);
|
|
pthread_mutex_lock(&os->lock);
|
|
mc_del(oss);
|
|
if (oss->stream && (oss->stream->session == oss)) // stream owner?
|
|
release_stream(oss->stream);
|
|
else
|
|
_release_session(oss);
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
static void release_no_sessions(struct ossess *oss)
|
|
{
|
|
struct octoserve *os = oss->os;
|
|
struct osstrm *str = oss->stream;
|
|
int i;
|
|
|
|
dbgprintf(DEBUG_SYS, "release session nr %d id %010d\n", oss->nr, oss->id);
|
|
pthread_mutex_lock(&os->lock);
|
|
mc_del(oss);
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
if (os->session[i].state &&
|
|
(os->session[i].stream == str) &&
|
|
(&os->session[i] != oss) &&
|
|
os->session[i].trans.mcast)
|
|
_release_session(&os->session[i]);
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
static uint32_t session_is_playing(struct ossess *sess)
|
|
{
|
|
struct osstrm *str = sess->stream;
|
|
struct octoserve *os = sess->os;
|
|
int i;
|
|
uint32_t playing = 0;
|
|
|
|
if (!(sess->trans.mcast && conform))
|
|
return sess->playing;
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_SESSION; i++)
|
|
if (os->session[i].state &&
|
|
os->session[i].trans.mcast &&
|
|
(os->session[i].stream == str)) {
|
|
playing |= os->session[i].playing;
|
|
dbgprintf(DEBUG_DEBUG, "playing[%u] = %u\n",
|
|
i, os->session[i].playing);
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
dbgprintf(DEBUG_DEBUG, "playing = %u\n", playing);
|
|
|
|
return playing;
|
|
}
|
|
|
|
static struct ossess *get_session(struct octoserve *os, uint32_t id)
|
|
{
|
|
int i;
|
|
struct ossess *oss;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
oss = &os->session[i];
|
|
if (oss->state && (oss->id == id)) {
|
|
pthread_mutex_unlock(&os->lock);
|
|
return oss;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static void check_mccs(struct ossess *sess)
|
|
{
|
|
time_t tdiff, t;
|
|
int update = 0;
|
|
|
|
if (!sess->trans.mcast)
|
|
return;
|
|
|
|
tdiff = mtime(&t) - sess->mcc_time;
|
|
switch (sess->mcc_state) {
|
|
case 1:
|
|
if (tdiff >= 1) {
|
|
sess->mcc_state = 2;
|
|
/* all replying clients get new tag */
|
|
sess->mcc_tag++;
|
|
send_igmp_query(sess->os, sess->trans.mcip, 10);
|
|
sess->mcc_time = t;
|
|
dbgprintf(DEBUG_IGMP, "%u: mcc_state 1 done, tag = %d\n", t, sess->mcc_tag);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (tdiff >= 1) {
|
|
sess->mcc_state = 3;
|
|
send_igmp_query(sess->os, sess->trans.mcip, 10);
|
|
dbgprintf(DEBUG_IGMP, "%u: mcc_state 2 done, tag = %d\n", t, sess->mcc_tag);
|
|
}
|
|
break;
|
|
case 3:
|
|
if (tdiff >= 2) {
|
|
sess->mcc_state = 0;
|
|
sess->mcc_time = t;
|
|
dbgprintf(DEBUG_IGMP,
|
|
"%u: mcc_state 3 done, tag = %d\n", t, sess->mcc_tag);
|
|
update = 1;
|
|
}
|
|
break;
|
|
case 0:
|
|
update = 1;
|
|
break;
|
|
}
|
|
mc_check(sess, update);
|
|
}
|
|
|
|
void session_timeout(struct ossess *sess)
|
|
{
|
|
mtime(&sess->timeout);
|
|
sess->timeout += sess->timeout_len;
|
|
dbgprintf(DEBUG_RTSP, "new timeout %d\n", sess->timeout);
|
|
}
|
|
|
|
void session_mc_timeout(struct ossess *sess)
|
|
{
|
|
mtime(&sess->mc_timeout);
|
|
sess->mc_timeout += 260;
|
|
dbgprintf(DEBUG_IGMP, "new mc timeout %d\n", sess->mc_timeout);
|
|
}
|
|
|
|
void check_session_timeouts(struct octoserve *os)
|
|
{
|
|
time_t t;
|
|
int i;
|
|
struct ossess *sess;
|
|
|
|
mtime(&t);
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
sess = &os->session[i];
|
|
check_mccs(sess);
|
|
|
|
if (sess->state &&
|
|
(sess->timeout < t)) {
|
|
struct oscon *con;
|
|
int j;
|
|
|
|
/* also count open connections referencing this session,
|
|
for VLC, mplayer, ... which do not send keep alives */
|
|
for (j = 0; j < MAX_CONNECT; j++) {
|
|
con = &os->con[j];
|
|
if (con->state && con->session &&
|
|
(con->session == sess)) {
|
|
session_timeout(sess);
|
|
break;
|
|
}
|
|
}
|
|
if (j == MAX_CONNECT)
|
|
release_session(&os->session[i]);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
static uint32_t get_id(struct octoserve *os)
|
|
{
|
|
uint32_t id, i;
|
|
struct ossess *oss;
|
|
|
|
while (1) {
|
|
id = random();
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
oss = &os->session[i];
|
|
if (oss->state && (oss->id == id))
|
|
break;
|
|
}
|
|
if (i == MAX_SESSION)
|
|
return id;
|
|
}
|
|
}
|
|
|
|
static struct ossess *alloc_session(struct octoserve *os)
|
|
{
|
|
int i;
|
|
struct ossess *oss;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
oss = &os->session[i];
|
|
if (oss->state == 0) {
|
|
memset(oss, 0, sizeof(struct ossess));
|
|
oss->os = os;
|
|
oss->nr = i;
|
|
oss->id = get_id(os);
|
|
os->sessionid++;
|
|
oss->state = 1;
|
|
oss->nsfd = -1;
|
|
oss->timeout_len = 60;
|
|
session_timeout(oss);
|
|
session_mc_timeout(oss);
|
|
LIST_INIT(&oss->mccs);
|
|
mtime(&oss->mcc_time);
|
|
dbgprintf(DEBUG_SYS,
|
|
"Allocated session nr=%d id=%d\n",
|
|
oss->nr, oss->id);
|
|
pthread_mutex_unlock(&os->lock);
|
|
return oss;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
return NULL;
|
|
}
|
|
|
|
static struct oscon *alloc_con(struct octoserve *os)
|
|
{
|
|
int i;
|
|
struct oscon *con;
|
|
|
|
for (i = 0; i < MAX_CONNECT; i++) {
|
|
con = &os->con[i];
|
|
if (con->state == 0) {
|
|
memset(con, 0, sizeof(struct oscon));
|
|
con->os = os;
|
|
con->nr = i;
|
|
con->state = 1;
|
|
return con;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int die(char *msg)
|
|
{
|
|
dbgprintf(DEBUG_SYS, "%s\n", msg);
|
|
exit(-1);
|
|
}
|
|
|
|
void sigchld_handler(int s)
|
|
{
|
|
while (wait(NULL) > 0);
|
|
}
|
|
|
|
static void send_option(struct oscon *con)
|
|
{
|
|
uint8_t buf[256], opt[256] ={ 0 };
|
|
int len;
|
|
|
|
if (con->session)
|
|
snprintf(opt, sizeof(opt), "Session: %010d\r\n", con->session->id);
|
|
|
|
len = snprintf(buf, sizeof(buf),
|
|
"RTSP/1.0 200 OK\r\n"
|
|
"CSeq: %d\r\n"
|
|
"Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN\r\n"
|
|
"%s"
|
|
"\r\n",
|
|
con->seq, opt);
|
|
if ((len > 0) && (len < sizeof(buf))) {
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send: %s\n", buf);
|
|
}
|
|
}
|
|
|
|
#define PAR2STR(par) ((p->set & (1UL << PARAM_##par)) ? par##2str[p->param[PARAM_POL]] : "")
|
|
|
|
char *pol2str[] = {"", "v", "h", "r", "l", NULL};
|
|
char *msys2str [] = {"", "undef", "dvbc", "j83b", "dvbt", "dss", "dvbs", "dvbs2", "dvbh",
|
|
"isdbt", "isdbs", "isdbc", "atsc", "atscmh", "dtmb", "cmmb", "dab",
|
|
"dvbt2", "turbo", "dvbcc", "dvbc2", NULL};
|
|
char *mtype2str [] = {"", "qpsk", "16qam", "32qam",
|
|
"64qam", "128qam", "256qam",
|
|
"autoqam", "8vsb", "16vsb", "8psk",
|
|
"16apsk", "32apsk", "dqpsk", "4qamnr",
|
|
"64apsk", "128apsk", "256apsk", NULL};
|
|
char *pilot2str [] = {"", "on", "off", "auto", NULL};
|
|
char *roll2str [] = {"", "0.35", "0.20", "0.25", NULL};
|
|
char *fec2str [] = {"", "none", "12", "23", "34", "45", "56", "67", "78", "89", "auto", "35", "910", "25", "14", "13", NULL};
|
|
char *bw2str [] = {"", "8", "7", "6", "auto", "5", "10", "1.712", NULL };
|
|
char *tmode2str [] = { "", "2k", "8k", "auto", "4k", "1k", "16k", "32k", "c1", "c3780", NULL};
|
|
char *gi2str [] = { "", "132", "116", "18", "14", "auto", "1128", "19128", "19256", "pn420", "pn595", "pn945", NULL};
|
|
char *num2str [] = {"", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL};
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
|
|
|
static int session_string(struct ossess *sess, char *msg, int mlen)
|
|
{
|
|
struct dvbfe *fe;
|
|
char pids[512];
|
|
struct dvb_params *p;
|
|
int pid, len, len2, plen;
|
|
int level = 0, lock = 0, quality = 0, tuner = 0;
|
|
int all;
|
|
struct osstrm *str = sess->stream;
|
|
|
|
if (!sess->state || !str)
|
|
return -1;
|
|
if (str->fe){
|
|
level = str->fe->level;
|
|
lock = str->fe->lock;
|
|
quality = (str->fe->quality * 3) / 20;
|
|
tuner = str->fe->nr;
|
|
}
|
|
p = &sess->p;
|
|
|
|
for (pid = 0, all = 1; pid < 1024; pid++) {
|
|
if (p->pid[pid] != 0xff) {
|
|
all = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (all)
|
|
snprintf(pids, sizeof(pids), "=all");
|
|
else {
|
|
for (pid = 0, plen = 0; pid < 8192; pid++) {
|
|
if (p->pid[pid >> 3] & (1 << (pid & 7))) {
|
|
len2 = snprintf(pids + plen, sizeof(pids) - plen,
|
|
",%u", pid);
|
|
if (len2 < 0)
|
|
return -1;
|
|
if (plen + len2 >= 200)
|
|
break;
|
|
plen += len2;
|
|
}
|
|
}
|
|
pids[0] = '=';
|
|
if (!plen)
|
|
snprintf(pids, sizeof(pids), "=none");
|
|
}
|
|
|
|
switch(p->param[PARAM_MSYS] - 1) {
|
|
case SYS_DVBS:
|
|
case SYS_DVBS2:
|
|
len = snprintf(msg, mlen,
|
|
"ver=1.0;src=%u;tuner=%u,%u,%u,%u,%u,%s,%s,%s,%s,%s,%u,%s;pids%s",
|
|
p->param[PARAM_SRC], tuner,
|
|
level, lock, quality,
|
|
p->param[PARAM_FREQ] / 1000,
|
|
pol2str[p->param[PARAM_POL]],
|
|
msys2str[p->param[PARAM_MSYS]],
|
|
mtype2str[p->param[PARAM_MTYPE]],
|
|
pilot2str[p->param[PARAM_PLTS]],
|
|
roll2str[p->param[PARAM_RO]],
|
|
p->param[PARAM_SR],
|
|
fec2str[p->param[PARAM_FEC]],
|
|
pids);
|
|
break;
|
|
case SYS_DVBT:
|
|
case SYS_DVBT2:
|
|
len = snprintf(msg, mlen,
|
|
"ver=1.1;tuner=%u,%u,%u,%u,%u.%03u,%s,%s,%s,%s,%s,%s,%u,%u,%u;pids%s",
|
|
tuner, level, lock, quality,
|
|
p->param[PARAM_FREQ] / 1000,
|
|
p->param[PARAM_FREQ] % 1000,
|
|
bw2str[p->param[PARAM_BW]],
|
|
msys2str[p->param[PARAM_MSYS]],
|
|
tmode2str[p->param[PARAM_TMODE]],
|
|
mtype2str[p->param[PARAM_MTYPE]],
|
|
gi2str[p->param[PARAM_GI]],
|
|
fec2str[p->param[PARAM_FEC]],
|
|
p->param[PARAM_PLP],
|
|
p->param[PARAM_T2ID],
|
|
p->param[PARAM_SM],
|
|
pids);
|
|
break;
|
|
case SYS_DVBC_ANNEX_A:
|
|
case (SYS_DVBC_ANNEX_C + 1):
|
|
#if 0
|
|
len = snprintf(msg, mlen,
|
|
"ver=0.9;tuner=%u,%u,%u,%u,%u.%03u,%u,%s,%s;pids%s",
|
|
tuner, level, lock, quality,
|
|
p->param[PARAM_FREQ] / 1000,
|
|
p->param[PARAM_FREQ] % 1000,
|
|
p->param[PARAM_SR],
|
|
msys2str[p->param[PARAM_MSYS]],
|
|
mtype2str[p->param[PARAM_MTYPE]],
|
|
pids);
|
|
#else
|
|
len = snprintf(msg, mlen,
|
|
"ver=1.2;tuner=%u,%u,%u,%u,%u.%03u,%u,%s,%s,%u,%u,%u,%u;pids%s",
|
|
tuner, level, lock, quality,
|
|
p->param[PARAM_FREQ] / 1000,
|
|
p->param[PARAM_FREQ] % 1000,
|
|
p->param[PARAM_BW_HZ] / 1000000,
|
|
msys2str[p->param[PARAM_MSYS]],
|
|
mtype2str[p->param[PARAM_MTYPE]],
|
|
p->param[PARAM_SR],
|
|
p->param[PARAM_C2TFT],
|
|
p->param[PARAM_DS],
|
|
p->param[PARAM_PLP],
|
|
pids);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if (len >= mlen)
|
|
return -1;
|
|
return len;
|
|
}
|
|
|
|
static int send_describe(struct oscon *con, int only)
|
|
{
|
|
struct osstrm *str;
|
|
struct ostrans *t;
|
|
uint8_t buf[4096], buf2[4096 + 1024], buf3[1024];
|
|
int len, len2, i;
|
|
int start = 0, end = MAX_STREAM;
|
|
char *p;
|
|
|
|
p = con->url;
|
|
while (*p && *p != ' ')
|
|
p++;
|
|
*p = 0;
|
|
|
|
#if 1
|
|
for (i = 0; i < MAX_STREAM; i++) {
|
|
str = &con->os->stream[i];
|
|
if (str->state)
|
|
break;
|
|
}
|
|
if (i == MAX_STREAM)
|
|
return -404;
|
|
#endif
|
|
if (only >= 0) {
|
|
if (only >= MAX_STREAM)
|
|
return -404;
|
|
start = only - 1;
|
|
end = only;
|
|
str = &con->os->stream[start];
|
|
if (!str->state)
|
|
return -404;
|
|
}
|
|
if (con->os->dvbtnum + con->os->dvbt2num + con->os->dvbcnum + con->os->dvbc2num)
|
|
len = snprintf(buf, sizeof(buf),
|
|
"v=0\r\n"
|
|
"o=- 5678901234 7890123456 IN %s %s\r\n"
|
|
"s=SatIPServer:1 %d,%d,%d\r\n"
|
|
"t=0 0\r\n",
|
|
con->trans.family == AF_INET ? "IP4" : "IP6",
|
|
con->sadr_ip,
|
|
con->os->dvbs2num,
|
|
con->os->dvbtnum + con->os->dvbt2num,
|
|
con->os->dvbcnum + con->os->dvbc2num
|
|
);
|
|
else
|
|
len = snprintf(buf, sizeof(buf),
|
|
"v=0\r\n"
|
|
"o=- 5678901234 7890123456 IN %s %s\r\n"
|
|
"s=SatIPServer:1 %d\r\n"
|
|
"t=0 0\r\n",
|
|
con->trans.family == AF_INET ? "IP4" : "IP6",
|
|
con->sadr_ip,
|
|
con->os->dvbs2num
|
|
);
|
|
|
|
if (len <= 0 || len >= sizeof(buf))
|
|
return -500;
|
|
for (i = start; i < end; i++) {
|
|
char *adr = "0.0.0.0", abuf[32];
|
|
int j, sendonly = 0;
|
|
int alen;
|
|
|
|
str = &con->os->stream[i];
|
|
if (!str->state)
|
|
continue;
|
|
t = &str->session->trans;
|
|
|
|
for (j = sendonly = 0; j < MAX_SESSION; j++) {
|
|
if (con->os->session[j].state &&
|
|
con->os->session[j].stream == str &&
|
|
con->os->session[j].playing)
|
|
sendonly = 1;
|
|
}
|
|
session_string(str->session, buf3, sizeof(buf3));
|
|
|
|
if (t->mcast) {
|
|
alen = snprintf(abuf, sizeof(abuf),
|
|
"%d.%d.%d.%d/%u",
|
|
t->mcip[0],
|
|
t->mcip[1],
|
|
t->mcip[2],
|
|
t->mcip[3],
|
|
t->ttl);
|
|
if (alen < 0)
|
|
return -500;
|
|
adr = abuf;
|
|
}
|
|
|
|
len2 = snprintf(buf + len, sizeof(buf) - len,
|
|
"m=video %d %s 33\r\n"
|
|
"c=IN %s %s\r\n"
|
|
"a=control:stream=%d\r\n"
|
|
"a=fmtp:33 %s\r\n"
|
|
"a=%s\r\n",
|
|
t->mcast ? t->cport : 0,
|
|
t->rtp ? "RTP/AVP" : "UDP",
|
|
t->family == AF_INET ? "IP4" : "IP6",
|
|
adr,
|
|
str->nr,
|
|
buf3,
|
|
sendonly ? "sendonly" : "inactive"
|
|
);
|
|
if (len2 <= 0 || len2 >= sizeof(buf) - len)
|
|
return -500;
|
|
len += len2;
|
|
}
|
|
len2 = sprintf(buf2, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
|
|
"Content-Type: application/sdp\r\n"
|
|
"Content-Base: rtsp://%s/\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"\r\n",
|
|
con->seq, con->sadr_ip, len);
|
|
|
|
/* URGH, some receivers (cough Kathr** cough) cannot handle
|
|
split RTSP messages */
|
|
#if 0
|
|
sendlen(con->sock, buf2, len2);
|
|
sendlen(con->sock, buf, len);
|
|
#else
|
|
memcpy(buf2 + len2, buf, len);
|
|
sendlen(con->sock, buf2, len + len2);
|
|
//dump(buf2, len + len2);
|
|
#endif
|
|
dbgprintf(DEBUG_RTSP, "Send:\n%s", buf2);
|
|
dbgprintf(DEBUG_RTSP, "%s\n", buf);
|
|
return 0;
|
|
}
|
|
|
|
static void send_describe2(struct oscon *con, char *url)
|
|
{
|
|
uint8_t buf[1024], buf2[1024], *p;
|
|
int len, len2;
|
|
|
|
//while (*url && *url != '?')
|
|
//url++;
|
|
p = url;
|
|
while (*p && *p != ' ')
|
|
p++;
|
|
*p = 0;
|
|
|
|
len = snprintf(buf, sizeof(buf),
|
|
"v=0\r\n"
|
|
"o=- 9876543210 1 IN IP4 %s\r\n"
|
|
"s=MPEG TS\r\n"
|
|
"t=0 0\r\n"
|
|
"m=video 0 RTP/AVP 33\r\n"
|
|
"c=IN IP4 0.0.0.0\r\n"
|
|
"a=control:%s\r\n",
|
|
con->sadr_ip,
|
|
url
|
|
);
|
|
if (len <= 0 || len >= sizeof(buf))
|
|
return;
|
|
len2=sprintf(buf2, "RTSP/1.0 200 OK\r\nCSeq: %d\r\nContent-Type: application/sdp\r\n"
|
|
"Content-Length: %d\r\n\r\n", con->seq, len);
|
|
|
|
sendlen(con->sock, buf2, len2);
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send:\n%s", buf2);
|
|
dbgprintf(DEBUG_RTSP, "%s\n", buf);
|
|
}
|
|
|
|
#define MAX_PID 32
|
|
|
|
static int getsparam(char *b, char *name, char *q[], struct dvb_params *p, int type)
|
|
{
|
|
int s, l = strlen(name), m;
|
|
|
|
if (strncasecmp(b, name, l))
|
|
return 0;
|
|
b += l;
|
|
if (b[0] == '&' || b[0] == ' ' || !b[m])
|
|
return l;
|
|
for (s = 1; q[s]; s++) {
|
|
m = strlen(q[s]);
|
|
if (!strncasecmp(b, q[s], m) &&
|
|
(b[m] == '&' || b[m] == ' ' || !b[m])) {
|
|
p->param[type] = s;
|
|
p->set |= (1UL << type);
|
|
dbgprintf(DEBUG_SYS, "%s%d(%s), ", name, p->param[type], q[s]);
|
|
return l + m;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int parse_url(struct oscon *con, int streamonly)
|
|
{
|
|
struct dvb_params *p = &con->p;
|
|
char *url, *end;
|
|
struct ossess *oss;
|
|
uint32_t pid;
|
|
int s, l;
|
|
|
|
dbgprintf(DEBUG_SYS, "parsing URL : %s\n", con->url);
|
|
memset(p, 0, sizeof(struct dvb_params));
|
|
|
|
for (url = con->url; *url == ' '; url++);
|
|
if (strncasecmp(url, "rtsp://", 7))
|
|
return -1;
|
|
for (url += 7; *url != '/'; url++)
|
|
if (*url == '\0')
|
|
return 0;
|
|
url++;
|
|
if (*url == '\0' || *url == ' ')
|
|
return 0;
|
|
if (*url != '?') {
|
|
if (strncasecmp(url, "stream=", 7))
|
|
return -1;
|
|
url += 7;
|
|
errno = 0;
|
|
p->param[PARAM_STREAMID] = strtoul(url, &end, 10);
|
|
if (errno)
|
|
return -1;
|
|
p->set |= (1UL << PARAM_STREAMID);
|
|
dbgprintf(DEBUG_SYS, "streamid = %d, ", p->param[PARAM_STREAMID]);
|
|
url = end;
|
|
}
|
|
if (*url != '\0' && *url != ' ' && *url != '?')
|
|
return -1;
|
|
if (!streamonly && *url == '?') {
|
|
do {
|
|
url++;
|
|
if (!strncasecmp(url, "src=", 4)) {
|
|
url += 4;
|
|
errno = 0;
|
|
p->param[PARAM_SRC] = strtoul(url, &end, 10);
|
|
if (url == end)
|
|
return -1;
|
|
p->set |= (1UL << PARAM_SRC);
|
|
dbgprintf(DEBUG_SYS, "src=%d, ", p->param[PARAM_SRC]);
|
|
} else if (!strncasecmp(url, "fe=", 3)) {
|
|
url += 3;
|
|
p->param[PARAM_FE] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_FE);
|
|
dbgprintf(DEBUG_SYS, "fe=%d, ", p->param[PARAM_FE]);
|
|
} else if (!strncasecmp(url, "c2tft=", 6)) {
|
|
url += 6;
|
|
p->param[PARAM_C2TFT] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_C2TFT);
|
|
dbgprintf(DEBUG_SYS, "c2tft=%d, ", p->param[PARAM_C2TFT]);
|
|
} else if (!strncasecmp(url, "ds=", 3)) {
|
|
url += 3;
|
|
p->param[PARAM_DS] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_DS);
|
|
dbgprintf(DEBUG_SYS, "ds=%d, ", p->param[PARAM_C2TFT]);
|
|
} else if (!strncasecmp(url, "plp=", 4)) {
|
|
url += 4;
|
|
p->param[PARAM_PLP] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_PLP);
|
|
dbgprintf(DEBUG_SYS, "plp=%d, ", p->param[PARAM_PLP]);
|
|
} else if (!strncasecmp(url, "specinv=", 8)) {
|
|
url += 8;
|
|
p->param[PARAM_SPECINV] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_SPECINV);
|
|
dbgprintf(DEBUG_SYS, "plp=%d, ", p->param[PARAM_PLP]);
|
|
} else if (!strncasecmp(url, "sm=", 3)) {
|
|
url += 3;
|
|
p->param[PARAM_SM] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_SM);
|
|
dbgprintf(DEBUG_SYS, "sm=%d, ", p->param[PARAM_SM]);
|
|
} else if (!strncasecmp(url, "t2id=", 5)) {
|
|
url += 5;
|
|
p->param[PARAM_T2ID] = strtoul(url, &end, 10);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_T2ID);
|
|
dbgprintf(DEBUG_SYS, "t2id=%d, ", p->param[PARAM_T2ID]);
|
|
} else if (!strncasecmp(url, "x_isi=", 6)) {
|
|
url += 6;
|
|
p->param[PARAM_ISI] = strtoul(url, &end, 0);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_ISI);
|
|
dbgprintf(DEBUG_SYS, "x_isi=%d, ", p->param[PARAM_ISI]);
|
|
} else if (!strncasecmp(url, "x_pls=", 6) ||
|
|
!strncasecmp(url, "x_ssi=", 6)) {
|
|
url += 6;
|
|
p->param[PARAM_PLS] = strtoul(url, &end, 0);
|
|
if (end == url)
|
|
break;
|
|
p->set |= (1UL << PARAM_PLS);
|
|
dbgprintf(DEBUG_SYS, "x_pls=%d, ", p->param[PARAM_PLS]);
|
|
} else if (!strncasecmp(url, "freq=", 5)) {
|
|
float f;
|
|
|
|
url += 5;
|
|
f = strtof(url, &end);
|
|
p->param[PARAM_FREQ] = f * 1000.0;
|
|
if (url == end)
|
|
return -1;
|
|
p->set |= (1UL << PARAM_FREQ);
|
|
dbgprintf(DEBUG_SYS, "freq=%u.%03u, ",
|
|
p->param[PARAM_FREQ] / 1000,
|
|
p->param[PARAM_FREQ] % 1000);
|
|
} else if ((l = getsparam(url, "pol=", pol2str, p, PARAM_POL))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "msys=", msys2str, p, PARAM_MSYS))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "ro=", roll2str, p, PARAM_RO))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "mtype=", mtype2str, p, PARAM_MTYPE))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "gi=", gi2str, p, PARAM_GI))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "tmode=", tmode2str, p, PARAM_TMODE))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if ((l = getsparam(url, "plts=", pilot2str, p, PARAM_PLTS))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if (!strncasecmp(url, "sr=", 3)) {
|
|
url += 3;
|
|
p->param[PARAM_SR] = strtoul(url, &end, 10);
|
|
p->set |= (1UL << PARAM_SR);
|
|
dbgprintf(DEBUG_SYS, "sr=%d, ", p->param[PARAM_SR]);
|
|
#if 0
|
|
} else if ((l = getsparam(url, "bw=", bw2str, p, PARAM_BW))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
#else
|
|
} else if (!strncasecmp(url, "bw=", 3)) {
|
|
float f;
|
|
|
|
url += 3;
|
|
f = strtof(url, &end);
|
|
p->param[PARAM_BW_HZ] = f * 1000000.0;
|
|
if (f == 5.0)
|
|
p->param[PARAM_BW] = BANDWIDTH_5_MHZ;
|
|
else if (f == 6.0)
|
|
p->param[PARAM_BW] = BANDWIDTH_6_MHZ;
|
|
else if (f == 7.0)
|
|
p->param[PARAM_BW] = BANDWIDTH_7_MHZ;
|
|
else if (f == 8.0)
|
|
p->param[PARAM_BW] = BANDWIDTH_8_MHZ;
|
|
else if (f == 10.0)
|
|
p->param[PARAM_BW] = BANDWIDTH_10_MHZ;
|
|
else if (f == 1.712)
|
|
p->param[PARAM_BW] = BANDWIDTH_1_712_MHZ;
|
|
else
|
|
return -1;
|
|
p->param[PARAM_BW]++;
|
|
if (url == end)
|
|
return -1;
|
|
p->set |= (1UL << PARAM_BW);
|
|
p->set |= (1UL << PARAM_BW_HZ);
|
|
#endif
|
|
} else if ((l = getsparam(url, "fec=", fec2str, p, PARAM_FEC))) {
|
|
if (l < 0)
|
|
return l;
|
|
end = url + l;
|
|
} else if (!strncasecmp(url, "pids=", 5)) {
|
|
if (p->set & ((1UL << PARAM_APID) | (1UL << PARAM_DPID)))
|
|
return -1;
|
|
url += 5;
|
|
memset(p->pid, 0, 0x400);
|
|
do {
|
|
pid = strtoul(url, &end, 10);
|
|
if (url == end) {
|
|
if (!strncasecmp(url, "all", 3)) {
|
|
memset(p->pid, 0xff, 0x400);
|
|
end = url + 3;
|
|
} else if (!strncasecmp(url, "none", 4)) {
|
|
memset(p->pid, 0x00, 0x400);
|
|
end = url + 4;
|
|
} else
|
|
return -1;
|
|
} else {
|
|
if (pid > 8191)
|
|
return -1;
|
|
p->pid[pid >> 3] |= (1 << (pid & 7));
|
|
}
|
|
url = end;
|
|
} while (*(url++) == ',');
|
|
p->set |= (1UL << PARAM_PID);
|
|
} else if (!strncasecmp(url, "addpids=", 8)) {
|
|
if (p->set & (1UL << PARAM_PID))
|
|
return -1;
|
|
url += 8;
|
|
do {
|
|
pid = strtoul(url, &end, 10);
|
|
if (url == end)
|
|
return -1;
|
|
url = end;
|
|
p->pid[pid >> 3] |= (1 << (pid & 7));
|
|
dbgprintf(DEBUG_SYS, "add PID=%d, ", pid);
|
|
} while (*(url++) == ',');
|
|
p->set |= (1UL << PARAM_APID);
|
|
} else if (!strncasecmp(url, "delpids=", 8)) {
|
|
if (p->set & (1UL << PARAM_PID))
|
|
return -1;
|
|
url += 8;
|
|
do {
|
|
pid = strtoul(url, &end, 10);
|
|
if (url == end)
|
|
return -1;
|
|
url = end;
|
|
p->dpid[pid >> 3] |= (1 << (pid & 7));
|
|
dbgprintf(DEBUG_SYS, "del PID=%d, ", pid);
|
|
} while (*(url++) == ',');
|
|
p->set |= (1UL << PARAM_DPID);
|
|
} else if (!strncasecmp(url, "x_pmt=", 6)) {
|
|
uint32_t pmt, sid, num = 0;
|
|
|
|
url += 6;
|
|
do {
|
|
pmt = strtoul(url, &end, 10);
|
|
if (url == end || pmt >= 8192)
|
|
return -1;
|
|
if (*end == '.') {
|
|
url = end + 1;
|
|
sid = strtoul(url, &end, 10);
|
|
if (url == end)
|
|
return -1;
|
|
pmt |= (sid << 16);
|
|
}
|
|
if (num < MAX_PMT)
|
|
p->pmt[num++] = pmt;
|
|
dbgprintf(DEBUG_SYS, "+PMT=%08x, ", pmt);
|
|
url = end;
|
|
} while (*(url++) == ',');
|
|
p->set |= (1UL << PARAM_PMT);
|
|
} else if (!strncasecmp(url, "x_ci=", 5)) {
|
|
url += 5;
|
|
p->param[PARAM_CI] = strtoul(url, &end, 10);
|
|
if (url == end)
|
|
return -1;
|
|
p->set |= (1UL << PARAM_CI);
|
|
dbgprintf(DEBUG_SYS, "ci=%d, ", p->param[PARAM_CI]);
|
|
} else {
|
|
dbgprintf(DEBUG_SYS, "unknown parameter %s\n", url);
|
|
while (*url != '&' && *url && *url != ' ')
|
|
url++;
|
|
end = url;
|
|
/* ignore unknown parameters: return -1; */
|
|
}
|
|
url = end;
|
|
} while (*url == '&');
|
|
}
|
|
while (*url == '/')
|
|
url++;
|
|
if (*url != ' ')
|
|
return -1;
|
|
while (*url == ' ')
|
|
url++;
|
|
if (strncasecmp(url, "RTSP/1.0", 8))
|
|
return -1;
|
|
dbgprintf(DEBUG_SYS, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static int set_rtcp_msg(struct ossess *sess)
|
|
{
|
|
char rtcp[512 - 96 - 102];
|
|
int len;
|
|
struct dvb_ns_rtcp rtcpm = { .msg = rtcp};
|
|
|
|
if (!sess->state)
|
|
return -1;
|
|
len = session_string(sess, rtcp, sizeof(rtcp));
|
|
if (len <= 0)
|
|
return -1;
|
|
rtcpm.len = len;
|
|
dbgprintf(DEBUG_RTSP, "%s\n", rtcp);
|
|
if (sess->nsfd < 0)
|
|
return -1;
|
|
return ioctl(sess->nsfd, NS_SET_RTCP_MSG, &rtcpm);
|
|
}
|
|
|
|
static int set_rtcp_msgs(struct osstrm *str)
|
|
{
|
|
char rtcp[512 - 96 - 102];
|
|
int len;
|
|
struct dvb_ns_rtcp rtcpm = { .msg = rtcp};
|
|
int i;
|
|
struct ossess *sess;
|
|
|
|
if (!str->state)
|
|
return -1;
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
sess = &str->os->session[i];
|
|
if (!sess->state)
|
|
continue;
|
|
len = session_string(sess, rtcp, sizeof(rtcp));
|
|
if (len <= 0)
|
|
continue;
|
|
rtcpm.len = len;
|
|
dbgprintf(DEBUG_RTSP, "%s\n", rtcp);
|
|
|
|
if (sess->stream && (sess->stream == str) && (sess->nsfd > 0))
|
|
ioctl(sess->nsfd, NS_SET_RTCP_MSG, &rtcpm);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int set_ns(struct ossess *sess, struct dvb_ns_params *nsp)
|
|
{
|
|
struct osstrm *str = sess->stream;
|
|
int fd = sess->nsfd;
|
|
uint8_t *pids = sess->p.pid;
|
|
|
|
if (fd < 0)
|
|
return 0;
|
|
dbgprintf(DEBUG_SYS, "%s\n", __FUNCTION__);
|
|
ioctl(fd, NS_SET_NET, nsp);
|
|
set_rtcp_msg(sess);
|
|
ioctl(fd, NS_SET_PIDS, &pids);
|
|
return 0;
|
|
}
|
|
|
|
static int get_ns(struct ossess *sess)
|
|
{
|
|
struct dvbfe *fe = sess->stream->fe;
|
|
char fname[80];
|
|
|
|
sprintf(fname, "/dev/dvb/adapter%d/ns%d", fe->anum, fe->fnum);
|
|
sess->nsfd = open(fname, O_RDWR);
|
|
if (sess->nsfd < 0 && errno != 2) {
|
|
dbgprintf(DEBUG_SYS, "no ns free\n");
|
|
return -1;
|
|
}
|
|
dbgprintf(DEBUG_SYS, "got fd %d, for %s\n", sess->nsfd, fname);
|
|
return 0;
|
|
}
|
|
|
|
static int setup_nsp(struct ostrans *trans, struct dvb_ns_params *nsp)
|
|
{
|
|
void *adr;
|
|
|
|
memset(nsp, 0, sizeof(struct dvb_ns_params));
|
|
memcpy(nsp->dmac, trans->cmac, 6);
|
|
memcpy(nsp->smac, trans->smac, 6);
|
|
#ifndef MAC_ODD
|
|
nsp->smac[5] |= 1;
|
|
#endif
|
|
nsp->sport = trans->sport;
|
|
nsp->sport2 = trans->sport2;
|
|
nsp->dport = trans->cport;
|
|
nsp->dport2 = trans->cport2;
|
|
if (trans->rtp) {
|
|
nsp->flags = DVB_NS_RTP | DVB_NS_RTCP;
|
|
if (!(trans->flags & TRANS_NO_RTP_TO))
|
|
nsp->flags |= DVB_NS_RTP_TO;
|
|
}
|
|
nsp->qos = 5;
|
|
nsp->vlan = 0;
|
|
nsp->ttl = 64;
|
|
dbgprintf(DEBUG_SYS, "ports: %d-%d %d-%d\n",
|
|
nsp->sport, nsp->sport2, nsp->dport, nsp->dport2);
|
|
|
|
memcpy(nsp->sip, trans->sip, 16);
|
|
memcpy(nsp->dip, trans->cip, 16);
|
|
|
|
if (trans->mcast) {
|
|
uint8_t *mac = adr;
|
|
|
|
memcpy(nsp->dmac, trans->mcmac, 6);
|
|
memcpy(nsp->dip, trans->mcip, 16);
|
|
|
|
nsp->ttl = trans->ttl ? trans->ttl : 2;
|
|
|
|
//dump(nsp->dmac, 6);
|
|
//dump(nsp->dip, 16);
|
|
dbgprintf(DEBUG_SYS, "MC STREAM\n");
|
|
}
|
|
memcpy(nsp->ssrc, trans->ssrc, 4);
|
|
return 0;
|
|
}
|
|
|
|
static int merge_pids(struct dvb_params *op, struct dvb_params *p)
|
|
{
|
|
int i, r = 0;
|
|
|
|
dbgprintf(DEBUG_SYS, "merge_pids\n");
|
|
|
|
if (p->set & (1UL << PARAM_PID))
|
|
for (i = 0; i < 1024; i++)
|
|
if (op->pid[i] != p->pid[i]) {
|
|
r |= 1;
|
|
op->pid[i] = p->pid[i];
|
|
}
|
|
if (p->set & (1UL << PARAM_APID))
|
|
for (i = 0; i < 1024; i++)
|
|
if ((~op->pid[i]) & p->pid[i]) {
|
|
r |= 2;
|
|
op->pid[i] |= p->pid[i];
|
|
}
|
|
if (p->set & (1UL << PARAM_DPID))
|
|
for (i = 0; i < 1024; i++)
|
|
if (op->pid[i] & p->dpid[i]) {
|
|
r |= 4;
|
|
op->pid[i] &= ~p->dpid[i];
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int merge_params(struct dvb_params *op, struct dvb_params *p)
|
|
{
|
|
int i;
|
|
|
|
if (p->set & 0xfff8) {
|
|
for (i = PARAM_FE ; i < PARAM_PID ; i++)
|
|
if (p->set & (1UL << i)) {
|
|
op->param[i] = p->param[i];
|
|
dbgprintf(DEBUG_SYS, "para %d = %d\n", i, p->param[i]);
|
|
op->set |= (1UL << i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void copy_params(struct ossess *s, struct ossess *t)
|
|
{
|
|
memcpy(&s->p, &t->p, sizeof(struct dvb_params));
|
|
}
|
|
|
|
static void adjust_no_sessions(struct ossess *oss)
|
|
{
|
|
struct octoserve *os = oss->os;
|
|
struct osstrm *str = oss->stream;
|
|
int i;
|
|
|
|
dbgprintf(DEBUG_SYS, "adjust session nr %d id %010d\n", oss->nr, oss->id);
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
struct ossess *nso = &os->session[i];
|
|
|
|
if (nso->state && (nso->stream == str) && (nso != oss)) {
|
|
uint8_t *pids = nso->p.pid;
|
|
|
|
memcpy(&nso->p, &oss->p, sizeof(struct dvb_params));
|
|
if (nso->nsfd >= 0)
|
|
ioctl(nso->nsfd, NS_SET_PIDS, &pids);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int setup_session(struct oscon *con, int newtrans)
|
|
{
|
|
struct dvbfe *fe;
|
|
struct dvb_ns_params nsp;
|
|
struct ossess *sess = con->session;
|
|
struct dvb_params *p = &con->p;
|
|
struct dvb_params *sp = &sess->p;
|
|
struct ostrans *trans = &con->trans;
|
|
struct osstrm *str = sess->stream;
|
|
struct ossess *ownsess = str->session;
|
|
int pidchange;
|
|
int owner = 0, tuned = 0;
|
|
|
|
if (sess == ownsess)
|
|
owner = 1;
|
|
if (!str)
|
|
return -500;
|
|
pidchange = merge_pids(sp, p);
|
|
|
|
dbgprintf(DEBUG_DEBUG, "stream setup newtrans=%u pidchange=%u\n",
|
|
newtrans, pidchange);
|
|
if (owner) { /* stream owner */
|
|
dbgprintf(DEBUG_DEBUG, "stream owner setup\n");
|
|
merge_params(sp, p);
|
|
|
|
if (str->fe && (sp->set & (1UL << PARAM_FE)) &&
|
|
(sp->param[PARAM_FE] != str->fe->nr)) {
|
|
release_fe(con->os, str->fe);
|
|
if (sess->nsfd >= 0)
|
|
close(sess->nsfd);
|
|
}
|
|
if (!str->fe) {
|
|
if (!(sp->set & (1UL << PARAM_MSYS)))
|
|
return 0;
|
|
if (sp->set & (1UL << PARAM_FE))
|
|
fe = alloc_fe_num(sess->os,
|
|
sp->param[PARAM_FE] - 1,
|
|
sp->param[PARAM_MSYS] - 1);
|
|
else
|
|
fe = alloc_fe(sess->os,
|
|
sp->param[PARAM_MSYS] - 1);
|
|
if (!fe) {
|
|
dbgprintf(DEBUG_SYS, "no fe\n");
|
|
return -404;
|
|
}
|
|
str->fe = fe;
|
|
dvb_tune(str->fe, sp);
|
|
tuned = 1;
|
|
} else if (p->set & 0xffff8) {
|
|
dvb_tune(str->fe, sp);
|
|
tuned = 1;
|
|
}
|
|
} else {
|
|
dbgprintf(DEBUG_DEBUG, "non stream owner setup\n");
|
|
if (pidchange && conform) {
|
|
/* non-owner tried to change pids,
|
|
copy back owner pids and return error */
|
|
copy_params(sess, ownsess);
|
|
return -455;
|
|
}
|
|
}
|
|
if (sess->nsfd < 0) {
|
|
dbgprintf(DEBUG_DEBUG, "no fd, conform=%u owner=%u mc=%u\n",
|
|
conform, owner, sess->trans.mcast);
|
|
if (conform && !owner && sess->trans.mcast) {
|
|
dbgprintf(DEBUG_DEBUG, "conform non-owner MCAST owner MC=%u\n",
|
|
ownsess->trans.mcast);
|
|
if (!ownsess->trans.mcast)
|
|
return -455;
|
|
} else {
|
|
dbgprintf(DEBUG_DEBUG, "owner or unicast get fd\n");
|
|
#ifndef IGNORE_NS
|
|
if (get_ns(sess) < 0)
|
|
return -455;
|
|
#endif
|
|
newtrans |= 256;
|
|
}
|
|
}
|
|
if (newtrans) {
|
|
if (sess->trans.mcast && conform) {
|
|
if (owner && (newtrans & ~1UL))
|
|
release_no_sessions(sess);
|
|
else {
|
|
memcpy(&sess->trans, &str->session->trans, sizeof(struct ostrans));
|
|
dbgprintf(DEBUG_RTSP, "non-owner tried to change transport parameters\n");
|
|
}
|
|
}
|
|
if (setup_nsp(&sess->trans, &nsp) < 0)
|
|
return -1;
|
|
if (set_ns(sess, &nsp) < 0)
|
|
return -1;
|
|
} else if (pidchange) {
|
|
uint8_t *pids = (sp->pid);
|
|
|
|
if (sess->nsfd >= 0)
|
|
ioctl(sess->nsfd, NS_SET_PIDS, &pids);
|
|
}
|
|
if (conform && owner && (pidchange || tuned))
|
|
adjust_no_sessions(sess);
|
|
|
|
if (p->set & ((1UL << PARAM_CI))) {
|
|
if (p->param[PARAM_CI] == 0 && str->ca)
|
|
release_ca(str->ca);
|
|
if (!str->ca) {
|
|
if (sess->playing)
|
|
return -455;
|
|
str->ca = alloc_ca_num(str, p->param[PARAM_CI] - 1);
|
|
}
|
|
if (!str->ca)
|
|
return -455;
|
|
}
|
|
if (p->set & ((1UL << PARAM_PMT))) {
|
|
if (!str->ca) {
|
|
if (sess->playing)
|
|
return -455;
|
|
str->ca = alloc_ca(str);
|
|
}
|
|
if (!str->ca)
|
|
return -455;
|
|
if (str->ca)
|
|
set_pmt(str->ca, p->pmt);
|
|
}
|
|
if (con->session->trans.mcast) {
|
|
update_switch_vec(sess);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int stop_session(struct ossess *sess)
|
|
{
|
|
if (!session_is_playing(sess))
|
|
return 0;
|
|
sess->playing &= ~1;
|
|
if (!session_is_playing(sess)) {
|
|
dbgprintf(DEBUG_RTSP, "stopping session %d\n", sess->nr);
|
|
if (sess->nsfd >= 0)
|
|
ioctl(sess->nsfd, NS_STOP);
|
|
}
|
|
update_switch_vec(sess);
|
|
return 0;
|
|
}
|
|
|
|
static int start_session(struct ossess *sess)
|
|
{
|
|
if (!session_is_playing(sess)) {
|
|
dbgprintf(DEBUG_RTSP, "start session %d\n", sess->nr);
|
|
if (sess->stream->ca) {
|
|
uint8_t canum = sess->stream->ca->nr - 1;
|
|
if (sess->nsfd >= 0)
|
|
ioctl(sess->nsfd, NS_SET_CI, &canum);
|
|
}
|
|
if (sess->nsfd >= 0)
|
|
ioctl(sess->nsfd, NS_START);
|
|
}
|
|
sess->playing |= 1;
|
|
update_switch_vec(sess);
|
|
return 0;
|
|
}
|
|
|
|
static int play_session(struct oscon *con)
|
|
{
|
|
int res;
|
|
struct ossess *sess = con->session;
|
|
struct dvb_params *p = &con->p;
|
|
struct ostrans *trans = &con->trans;
|
|
struct octoserve *os = sess->os;
|
|
struct osstrm *str = sess->stream;
|
|
|
|
if (p) {
|
|
res = setup_session(con, 0);
|
|
if (res < 0)
|
|
return res;
|
|
}
|
|
dbgprintf(DEBUG_SYS, "%s fd %d\n", __FUNCTION__, sess->nsfd);
|
|
pthread_mutex_lock(&os->lock);
|
|
/* for strict multicast there is only the stream owners stream */
|
|
if (sess->trans.mcast && conform) {
|
|
#ifndef IGNORE_NS
|
|
if (str->session->nsfd < 0)
|
|
return -455;
|
|
#endif
|
|
start_session(str->session);
|
|
} else {
|
|
#ifndef IGNORE_NS
|
|
if (sess->nsfd < 0)
|
|
return -455;
|
|
#endif
|
|
start_session(sess);
|
|
}
|
|
dbgprintf(DEBUG_SYS, "%s set playing =2\n", __FUNCTION__);
|
|
sess->playing |= 2;
|
|
pthread_mutex_unlock(&os->lock);
|
|
update_switch_vec(sess);
|
|
return 0;
|
|
}
|
|
|
|
static struct ossess *match_session(struct octoserve *os, uint8_t *group)
|
|
{
|
|
struct ossess *sess;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
sess = &os->session[i];
|
|
if (!sess->state || !sess->trans.mcast)
|
|
continue;
|
|
if (conform && sess->stream->session != sess)
|
|
continue;
|
|
if (!memcmp(sess->trans.mcip, group, 4))
|
|
return sess;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void mc_check(struct ossess *sess, int update)
|
|
{
|
|
struct osmcc *mcc, *next;
|
|
struct octoserve *os = sess->os;
|
|
|
|
if (sess->mcc_state && (os->igmp_mode & 1))
|
|
return;
|
|
pthread_mutex_lock(&os->lock);
|
|
for (mcc = sess->mccs.lh_first; mcc; mcc = next) {
|
|
next = mcc->mcc.le_next;
|
|
if (((sess->mcc_state == 0) && (sess->mcc_tag != mcc->tag)) ||
|
|
(((os->igmp_mode & 1) == 0) && (os->igmp_tag - mcc->gtag) > 1)) {
|
|
dbgprintf(DEBUG_IGMP, "removed client at %u.%u.%u.%u\n",
|
|
mcc->ip[0], mcc->ip[1], mcc->ip[2], mcc->ip[3]);
|
|
dbgprintf(DEBUG_IGMP, "mcc_tags: %d %d\n", sess->mcc_tag, mcc->tag);
|
|
dbgprintf(DEBUG_IGMP, "gtags: %d %d\n", os->igmp_tag, mcc->gtag);
|
|
LIST_REMOVE(mcc, mcc);
|
|
free(mcc);
|
|
}
|
|
}
|
|
if (update) {
|
|
update_switch_vec(sess);
|
|
if (!sess->mccs.lh_first)
|
|
//if (mtime() > sess->mc_timeout)
|
|
stop_session(sess);
|
|
}
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
void mc_del(struct ossess *sess)
|
|
{
|
|
struct osmcc *mcc, *next;
|
|
struct octoserve *os = sess->os;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
for (mcc = sess->mccs.lh_first; mcc; mcc = next) {
|
|
next = mcc->mcc.le_next;
|
|
LIST_REMOVE(mcc, mcc);
|
|
free(mcc);
|
|
}
|
|
update_switch_vec(sess);
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
static void killall_sessions(struct octoserve *os)
|
|
{
|
|
struct ossess *sess;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_SESSION; i++) {
|
|
sess = &os->session[i];
|
|
if (!sess->state)
|
|
continue;
|
|
release_session(sess);
|
|
}
|
|
}
|
|
|
|
void mc_join(struct octoserve *os, uint8_t *ip, uint8_t *mac, uint8_t *group)
|
|
{
|
|
struct ossess *sess;
|
|
struct osmcc *mcc, *newmcc;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
if ((sess = match_session(os, group)) == NULL)
|
|
goto out;
|
|
|
|
session_mc_timeout(sess);
|
|
dbgprintf(DEBUG_IGMP, "matched session %d to join %u.%u.%u.%u\n",
|
|
sess->nr, ip[0], ip[1], ip[2], ip[3]);
|
|
for (mcc = sess->mccs.lh_first; mcc; mcc = mcc->mcc.le_next)
|
|
if (!memcmp(ip, mcc->ip, 4)) {
|
|
mcc->tag = sess->mcc_tag;
|
|
mcc->gtag = os->igmp_tag;
|
|
dbgprintf(DEBUG_IGMP,
|
|
"already in list, tag = %08x, gtag = %08x\n", mcc->tag, mcc->gtag);
|
|
goto out;
|
|
}
|
|
newmcc = malloc(sizeof(struct osmcc));
|
|
if (!newmcc) {
|
|
dbgprintf(DEBUG_IGMP, "Could not allocate new multicast client entry\n");
|
|
goto out;
|
|
}
|
|
memset(newmcc, 0, sizeof(struct osmcc));
|
|
LIST_INSERT_HEAD(&sess->mccs, newmcc, mcc);
|
|
memcpy(newmcc->ip, ip, 4);
|
|
newmcc->tag = sess->mcc_tag;
|
|
newmcc->gtag = os->igmp_tag;
|
|
if (mac) {
|
|
int port;
|
|
|
|
memcpy(newmcc->mac, mac, 6);
|
|
if (os->has_switch)
|
|
port = switch_get_port(newmcc->mac);
|
|
else
|
|
port = 0;
|
|
dbgprintf(DEBUG_IGMP, "New client at port %02x\n", port);
|
|
if (port >= 0) {
|
|
newmcc->port_vec = port;
|
|
sess->mcc_port_vec |= port;
|
|
}
|
|
update_switch_vec(sess);
|
|
}
|
|
start_session(sess);
|
|
out:
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
void mc_query(struct ossess *sess)
|
|
{
|
|
/* query in group if anybody still there */
|
|
if (!sess->mcc_state) {
|
|
mtime(&sess->mcc_time);
|
|
sess->mcc_state = 1;
|
|
}
|
|
}
|
|
|
|
void mc_leave(struct octoserve *os, uint8_t *ip, uint8_t *group)
|
|
{
|
|
struct ossess *sess;
|
|
struct osmcc *mcc, *next;
|
|
|
|
pthread_mutex_lock(&os->lock);
|
|
if ((sess = match_session(os, group)) == NULL)
|
|
goto out;
|
|
|
|
dbgprintf(DEBUG_IGMP, "matched session %d to leave %u.%u.%u.%u\n",
|
|
sess->nr, ip[0], ip[1], ip[2], ip[3]);
|
|
mc_query(sess);
|
|
|
|
for (mcc = sess->mccs.lh_first; mcc; mcc = next) {
|
|
next = mcc->mcc.le_next;
|
|
if (!memcmp(ip, mcc->ip, 4)) {
|
|
LIST_REMOVE(mcc, mcc);
|
|
free(mcc);
|
|
#if 0
|
|
if (!sess->mccs.lh_first)
|
|
stop_session(sess);
|
|
mc_query(sess);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
update_switch_vec(sess);
|
|
out:
|
|
pthread_mutex_unlock(&os->lock);
|
|
}
|
|
|
|
static void send_play(struct oscon *con)
|
|
{
|
|
uint8_t buf[1024];
|
|
int len;
|
|
|
|
len = sprintf(buf,
|
|
"RTSP/1.0 200 OK\r\n"
|
|
"CSeq: %d\r\n"
|
|
"Session: %010d\r\n"
|
|
"RTP-Info: url=%s\r\n"
|
|
"\r\n", con->seq, con->session->id, con->url);
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send: %s\n", buf);
|
|
}
|
|
|
|
static void send_teardown(struct oscon *con)
|
|
{
|
|
uint8_t buf[256];
|
|
int len;
|
|
|
|
len=sprintf(buf,
|
|
"RTSP/1.0 200 OK\r\n"
|
|
"CSeq: %d\r\n"
|
|
"Session: %010d\r\n"
|
|
"\r\n", con->seq, con->session->id);
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send: %s\n", buf);
|
|
}
|
|
|
|
static int parse_transport(struct oscon *con, char *line)
|
|
{
|
|
char *end, *arg;
|
|
struct ostrans *t = &con->trans;
|
|
int ttlset = 0;
|
|
|
|
dbgprintf(DEBUG_RTSP, "parse: %s\n", line);
|
|
|
|
for (arg = line; *arg == ' '; arg++);
|
|
|
|
do {
|
|
dbgprintf(DEBUG_RTSP, "arg:%s\n", arg);
|
|
if (!strncasecmp(arg, "RTP/AVP/UDP", 11)) {
|
|
t->rtp = 1;
|
|
end = arg + 11;
|
|
} else if (!strncasecmp(arg, "RTP/AVP", 7)) {
|
|
t->rtp = 1;
|
|
end = arg + 7;
|
|
} else if (!strncasecmp(arg, "UDP", 3)) {
|
|
t->rtp = 0;
|
|
end = arg + 3;
|
|
} else if (!strncasecmp(arg, "unicast", 7)) {
|
|
t->mcast = 0;
|
|
end = arg + 7;
|
|
} else if (!strncasecmp(arg, "multicast", 9)) {
|
|
t->mcast = 1;
|
|
end = arg + 9;
|
|
} else if (!strncasecmp(arg, "client_port=", 12)) {
|
|
arg += 12;
|
|
t->cport = strtoul(arg, &end, 10);
|
|
if (*end == '-')
|
|
t->cport2 = strtoul(end + 1, &end, 10);
|
|
else
|
|
t->cport2 = 0;
|
|
} else if (!strncasecmp(arg, "port=", 5)) {
|
|
arg += 5;
|
|
t->cport = strtoul(arg, &end, 10);
|
|
if (*end == '-')
|
|
t->cport2 = strtoul(end + 1, &end, 10);
|
|
else
|
|
t->cport2 = 0;
|
|
} else if (!strncasecmp(arg, "interleaved=", 12)) {
|
|
return -461;
|
|
arg += 12;
|
|
strtoul(arg, &end, 10);
|
|
strtoul(end + 1, &end, 10);
|
|
} else if (!strncasecmp(arg, "ttl=", 4)) {
|
|
arg += 4;
|
|
t->ttl = strtoul(arg, &end, 10);
|
|
ttlset = 1;
|
|
} else if (!strncasecmp(arg, "destination=", 12)) {
|
|
char c;
|
|
|
|
arg += 12;
|
|
end = arg;
|
|
while (*end && *end != ';' && *end != ' ')
|
|
end++;
|
|
c = *end;
|
|
*end = 0;
|
|
inet_pton(AF_INET, arg, &t->mcip);
|
|
t->mcmac[0] = 0x01;
|
|
t->mcmac[1] = 0x00;
|
|
t->mcmac[2] = 0x5e;
|
|
t->mcmac[3] = t->mcip[1] & 0x7f;
|
|
t->mcmac[4] = t->mcip[2];
|
|
t->mcmac[5] = t->mcip[3];
|
|
*end = c;
|
|
t->flags |= TRANS_ALT_DEST;
|
|
} else
|
|
return -461;
|
|
arg = end;
|
|
if (*arg != ';' && *arg != ' ' && *arg != 0)
|
|
return -461;
|
|
} while (*(arg++) == ';');
|
|
|
|
if (t->flags & TRANS_ALT_DEST && !t->mcast) {
|
|
ip2mac(t->mcip, t->mcmac);
|
|
|
|
}
|
|
if (!t->ttl && !ttlset)
|
|
t->ttl = 2;
|
|
dbgprintf(DEBUG_RTSP, "parsed\n");
|
|
return 0;
|
|
}
|
|
|
|
static int parse_x_octonet(struct oscon *con, char *line)
|
|
{
|
|
char *end, *arg;
|
|
|
|
dbgprintf(DEBUG_RTSP, "parse: %s\n", line);
|
|
|
|
for (arg = line; *arg == ' '; arg++);
|
|
|
|
do {
|
|
if (!strncasecmp(arg, "switch=", 7)) {
|
|
uint32_t port;
|
|
|
|
arg += 7;
|
|
do {
|
|
port = strtoul(arg, &end, 10);
|
|
if (arg == end)
|
|
return -1;
|
|
arg = end;
|
|
if (port < 7)
|
|
con->x_ports |= 1 << (port - 1);
|
|
} while (*(arg++) == ',');
|
|
dbgprintf(DEBUG_SWITCH, "x_ports = %02x\n", con->x_ports);
|
|
} else
|
|
return -1;
|
|
arg = end;
|
|
} while (*(arg++) == ';');
|
|
dbgprintf(DEBUG_RTSP, "parsed\n");
|
|
return 0;
|
|
}
|
|
|
|
void send_setup(struct oscon *con)
|
|
{
|
|
uint8_t buf[256];
|
|
int len;
|
|
struct ostrans *t = &con->session->trans;
|
|
|
|
if (t->mcast)
|
|
len = sprintf(buf,
|
|
"RTSP/1.0 200 OK\r\n"
|
|
"CSeq: %d\r\nSession: %010d;timeout=%u\r\n"
|
|
"Transport: %s;multicast;destination=%d.%d.%d.%d;"
|
|
"port=%d-%d;ttl=%d;source=%s\r\n"
|
|
"com.ses.streamID: %d\r\n\r\n",
|
|
con->seq, con->session->id, con->session->timeout_len,
|
|
t->rtp ? "RTP/AVP" : "UDP",
|
|
t->mcip[0], t->mcip[1], t->mcip[2], t->mcip[3],
|
|
t->cport, t->cport2, t->ttl,
|
|
con->sadr_ip, con->session->stream->nr
|
|
);
|
|
else
|
|
len = sprintf(buf,
|
|
"RTSP/1.0 200 OK\r\n"
|
|
"CSeq: %d\r\nSession: %010d;timeout=%u\r\n"
|
|
"Transport: %s;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
|
|
"com.ses.streamID: %d\r\n\r\n",
|
|
con->seq, con->session->id, con->session->timeout_len,
|
|
t->rtp ? "RTP/AVP" : "UDP",
|
|
t->cport, t->cport2,
|
|
con->session->stream->sport,
|
|
con->session->stream->sport2,
|
|
con->session->stream->nr
|
|
);
|
|
sendlen(con->sock, buf, len);
|
|
dbgprintf(DEBUG_RTSP, "Send: %s\n", buf);
|
|
}
|
|
|
|
static void cpyarg(char *d, char *s)
|
|
{
|
|
while (*s == ' ')
|
|
s++;
|
|
strcpy(d, s);
|
|
}
|
|
|
|
|
|
static int cmp_trans(struct ostrans *t, struct ostrans *u)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
if (t->ttl != u->ttl)
|
|
ret |= 1;
|
|
if (t->mcast != u->mcast)
|
|
ret |= 2;
|
|
if (t->cport != u->cport)
|
|
ret |= 4;
|
|
if (t->cport2 != u->cport2)
|
|
ret |= 8;
|
|
if (t->flags != u->flags)
|
|
ret |= 16;
|
|
return ret;
|
|
}
|
|
|
|
static int proc_setup(struct oscon *con)
|
|
{
|
|
struct osstrm *str = 0;
|
|
uint32_t newtrans = 0;
|
|
int res;
|
|
|
|
if (!con->transport_parsed) {
|
|
/* no proper transport params given */
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
if (con->session_parsed && !con->session) {
|
|
/* no session with given ID found */
|
|
send_error(con, 454);
|
|
return -1;
|
|
}
|
|
if (parse_url(con, 0) < 0) {
|
|
/* invalid params in URL */
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
if (con->p.set & (1UL << PARAM_STREAMID)) {
|
|
dbgprintf(DEBUG_SYS, "existing stream %d\n",
|
|
con->p.param[PARAM_STREAMID]);
|
|
str = get_stream(con->os, con->p.param[PARAM_STREAMID]);
|
|
if (!str) {
|
|
/* no stream with given ID */
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
if (con->session && con->session != str->session) {
|
|
/* if we already have a session ID we
|
|
have to be stream owner */
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
if (!con->session) {
|
|
/* alloc new session */
|
|
con->session = alloc_session(con->os);
|
|
if (!con->session) {
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
con->state = 2;
|
|
if (!con->session->stream) {
|
|
if (str) {
|
|
/* use existing stream and stream params*/
|
|
con->session->stream = str;
|
|
if (con->session->stream->session) {
|
|
memcpy(&con->session->p,
|
|
&con->session->stream->session->p,
|
|
sizeof(struct dvb_params));
|
|
}
|
|
} else {
|
|
con->session->stream = alloc_stream(con->os);
|
|
if (!con->session->stream) {
|
|
send_error(con, 400);
|
|
return -1;
|
|
}
|
|
con->session->stream->session = con->session;
|
|
}
|
|
}
|
|
newtrans = 256;
|
|
}
|
|
newtrans |= cmp_trans(&con->session->trans, &con->trans);
|
|
if (newtrans) {
|
|
con->trans.sport = con->session->stream->sport;
|
|
con->trans.sport2 = con->session->stream->sport2;
|
|
|
|
/* set transport struct according to session
|
|
and transport parameters */
|
|
if (con->trans.mcast && con->trans.cport == 0) {
|
|
con->trans.cport = con->trans.sport;
|
|
con->trans.cport2 = con->trans.sport2;
|
|
}
|
|
|
|
if (con->trans.mcast &&
|
|
!(con->trans.flags & TRANS_ALT_DEST)) {
|
|
uint8_t mac[6] = { 0x01, 0x00, 0x5e,
|
|
con->os->ssdp.devid & 0x7f, 1,
|
|
con->session->stream->nr };
|
|
uint8_t ip[4] = { 239, con->os->ssdp.devid, 1,
|
|
con->session->stream->nr };
|
|
|
|
memcpy(con->trans.mcmac, mac, 6);
|
|
memcpy(con->trans.mcip, ip, 4);
|
|
}
|
|
con->session->trans = con->trans;
|
|
}
|
|
res = setup_session(con, newtrans);
|
|
if (res < 0) {
|
|
release_session(con->session);
|
|
send_error(con, -res);
|
|
} else
|
|
send_setup(con);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int proc_line(struct oscon *con)
|
|
{
|
|
char *line = con->buf;
|
|
int res;
|
|
|
|
if (con->ln == 0) {
|
|
if (!strncasecmp(line, "OPTIONS", 7)) {
|
|
cpyarg(con->url, line + 7);
|
|
con->cmd = M_OPTIONS;
|
|
} else if (!strncasecmp(line, "DESCRIBE", 8)) {
|
|
cpyarg(con->url, line + 8);
|
|
con->cmd = M_DESCRIBE;
|
|
} else if (!strncasecmp(line, "SETUP", 5)) {
|
|
cpyarg(con->url, line + 5);
|
|
con->cmd = M_SETUP;
|
|
} else if (!strncasecmp(line, "PLAY", 4)) {
|
|
cpyarg(con->url, line + 4);
|
|
con->cmd = M_PLAY;
|
|
} else if (!strncasecmp(line, "TEARDOWN", 8)) {
|
|
cpyarg(con->url, line + 8);
|
|
con->cmd = M_TEARDOWN;
|
|
} else {
|
|
con->cmd = M_UNKNOWN;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (line[0] == '\0') { /* last line */
|
|
if (con->error) {
|
|
send_error(con, con->error);
|
|
} else {
|
|
switch (con->cmd) {
|
|
case M_OPTIONS:
|
|
if (con->session_parsed && !con->session)
|
|
send_error(con, 404);
|
|
else
|
|
send_option(con);
|
|
break;
|
|
case M_DESCRIBE:
|
|
{
|
|
int only = -1;
|
|
|
|
if (parse_url(con, 0) < 0) {
|
|
send_error(con, 400);
|
|
break;
|
|
}
|
|
if (con->p.set & ~(1UL << PARAM_STREAMID)) {
|
|
send_describe2(con, con->url);
|
|
break;
|
|
}
|
|
if (con->p.set & (1UL << PARAM_STREAMID))
|
|
only = con->p.param[PARAM_STREAMID];
|
|
res = send_describe(con, only);
|
|
if (res < 0)
|
|
send_error(con, -res);
|
|
break;
|
|
}
|
|
|
|
case M_SETUP:
|
|
proc_setup(con);
|
|
break;
|
|
|
|
case M_PLAY:
|
|
if (!con->session || !con->session->stream) {
|
|
send_error(con, 400);
|
|
break;
|
|
}
|
|
if (parse_url(con, 0) < 0) {
|
|
send_error(con, 400);
|
|
break;
|
|
}
|
|
if (con->x_octonet_parsed) {
|
|
con->session->port_vec = con->x_ports;
|
|
dbgprintf(DEBUG_SWITCH, "port_vec = %02x\n", con->session->port_vec);
|
|
}
|
|
res = play_session(con);
|
|
if (res < 0)
|
|
send_error(con, -res);
|
|
else
|
|
send_play(con);
|
|
break;
|
|
case M_TEARDOWN:
|
|
if (parse_url(con, 0) < 0) {
|
|
send_error(con, 400);
|
|
break;
|
|
}
|
|
if (!con->session) {
|
|
send_error(con, 404);
|
|
break;
|
|
}
|
|
send_teardown(con);
|
|
release_session(con->session);
|
|
break;
|
|
default:
|
|
send_error(con, 501);
|
|
break;
|
|
}
|
|
}
|
|
con->cmd = M_NONE;
|
|
con->transport_parsed = 0;
|
|
con->session_parsed = 0;
|
|
con->error = 0;
|
|
con->ln = -1;
|
|
return 0;
|
|
}
|
|
|
|
if (!strncasecmp(line, "CSeq:", 5)) {
|
|
con->seq = strtoul(line + 5, NULL, 10);
|
|
//printf("CSeq = %d\n", con->seq);
|
|
} else if (!strncasecmp(line, "Transport:", 10)) {
|
|
res = parse_transport(con, line + 10);
|
|
if (res < 0) {
|
|
con->error = -res;
|
|
} else
|
|
con->transport_parsed = 1;
|
|
} else if (!strncasecmp(line, "Session:", 8)) {
|
|
uint32_t sid = strtoul(line + 8, NULL, 10);
|
|
|
|
con->session = get_session(con->os, sid);
|
|
if (con->session)
|
|
session_timeout(con->session);
|
|
con->state = 2;
|
|
con->session_parsed = 1;
|
|
} else if (!strncasecmp(line, "User-Agent:", 11)) {
|
|
char *p = line + 11;
|
|
|
|
while (*p == ' ')
|
|
p++;
|
|
if (!strncasecmp(p, "libvlc", 6))
|
|
con->trans.flags |= TRANS_NO_RTP_TO;
|
|
} else if (!strncasecmp(line, "x_octonet:", 10)) {
|
|
char *p = line + 10;
|
|
|
|
while (*p == ' ')
|
|
p++;
|
|
parse_x_octonet(con, p);
|
|
con->x_octonet_parsed = 1;
|
|
} else
|
|
;//con->error = 551;
|
|
return 0;
|
|
}
|
|
|
|
static const char *intop(int af, struct sockaddr *sa, char *dst, socklen_t size, uint8_t *ip)
|
|
{
|
|
void *adr;
|
|
|
|
if (af == AF_INET)
|
|
adr = &((struct sockaddr_in *) sa)->sin_addr;
|
|
else if (af == AF_INET6)
|
|
adr = &((struct sockaddr_in6 *) sa)->sin6_addr;
|
|
else
|
|
return NULL;
|
|
|
|
memcpy(ip, adr, af == AF_INET ? 4 : 16);
|
|
return inet_ntop(af, adr, dst, size);
|
|
}
|
|
|
|
static void get_ips(struct oscon *con)
|
|
{
|
|
con->slen = sizeof(con->sadr);
|
|
con->clen = sizeof(con->cadr);
|
|
getsockname(con->sock, (struct sockaddr *) &con->sadr, &con->slen);
|
|
intop(con->trans.family, &con->sadr, con->sadr_ip, INET6_ADDRSTRLEN, con->trans.sip);
|
|
getpeername(con->sock, (struct sockaddr *) &con->cadr, &con->clen);
|
|
intop(con->trans.family, &con->cadr, con->cadr_ip, INET6_ADDRSTRLEN, con->trans.cip);
|
|
}
|
|
|
|
static int same_ip(struct sockaddr *sa, struct sockaddr *sa2)
|
|
{
|
|
unsigned short af = sa->sa_family;
|
|
|
|
if (af != sa2->sa_family)
|
|
return -1;
|
|
if (af == AF_INET)
|
|
return memcmp(&((struct sockaddr_in *) sa)->sin_addr,
|
|
&((struct sockaddr_in *) sa2)->sin_addr, 4);
|
|
else if (af == AF_INET6)
|
|
return memcmp(&((struct sockaddr_in6 *) sa)->sin6_addr,
|
|
&((struct sockaddr_in6 *) sa2)->sin6_addr, 16);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
static void init_con(struct oscon *con)
|
|
{
|
|
int one = 1, idle = 20, intvl = 20, cnt = 3;
|
|
|
|
get_ips(con);
|
|
if (!same_ip(&con->sadr, &con->cadr))
|
|
memcpy(con->trans.cmac, con->os->mac, 6);
|
|
else
|
|
get_mac(con->os->ifname, &con->cadr, con->trans.cmac);
|
|
memcpy(con->trans.smac, con->os->mac, 6);
|
|
con->bufp = 0;
|
|
con->ln = 0;
|
|
con->session = 0;
|
|
setsockopt(con->sock, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
|
|
#if 1
|
|
setsockopt(con->sock, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
|
|
setsockopt(con->sock, SOL_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
|
|
setsockopt(con->sock, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
|
|
#else
|
|
setsockopt(con->sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
|
|
setsockopt(con->sock, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
|
|
setsockopt(con->sock, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
static void con_check(struct octoserve *os)
|
|
{
|
|
int i;
|
|
time_t t = mtime(NULL);
|
|
|
|
for (i = 0; i < MAX_CONNECT; i++) {
|
|
if (os->con[i].state == 3 &&
|
|
t > os->con[i].timeout)
|
|
release_con(&os->con[i]);
|
|
}
|
|
}
|
|
|
|
int con_loop(struct oscon *con)
|
|
{
|
|
uint8_t buf[1024];
|
|
int len, i, res;
|
|
|
|
len = recv(con->sock, buf, 1024, 0);
|
|
if (len == 0)
|
|
goto release;
|
|
if (len < 0) {
|
|
if (errno != EAGAIN)
|
|
goto release;
|
|
return 0;
|
|
}
|
|
if (debug & DEBUG_RTSP)
|
|
dump(buf, len);
|
|
//printf("received %d bytes\n", len);
|
|
for (i = 0; i < len; i++) {
|
|
// FIXME send URI too long
|
|
if (con->bufp >= 8192) {
|
|
goto release;
|
|
}
|
|
con->buf[con->bufp++] = buf[i];
|
|
if (buf[i] == '\n') {
|
|
if (con->bufp < 2 ||
|
|
con->buf[con->bufp - 2] != '\r') {
|
|
goto release;
|
|
}
|
|
con->buf[con->bufp - 2] = 0;
|
|
if (con->buf[0])
|
|
dbgprintf(DEBUG_SYS, "%d:%d:%d: %s\n",
|
|
con->nr, con->ln,
|
|
con->bufp, con->buf);
|
|
res = proc_line(con);
|
|
con->ln++;
|
|
con->bufp = 0;
|
|
}
|
|
}
|
|
return 0;
|
|
release:
|
|
dbgprintf(DEBUG_SYS, "release\n");
|
|
release_con(con);
|
|
return -1;
|
|
}
|
|
|
|
void handle_con(struct oscon *con)
|
|
{
|
|
while (con_loop(con) >= 0);
|
|
}
|
|
|
|
int set_nonblock(int fd)
|
|
{
|
|
int fl = fcntl(fd, F_GETFL);
|
|
|
|
if (fl < 0)
|
|
return fl;
|
|
return fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
|
}
|
|
|
|
int set_block(int fd)
|
|
{
|
|
int fl = fcntl(fd, F_GETFL);
|
|
|
|
if (fl < 0)
|
|
return fl;
|
|
return fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
|
|
}
|
|
|
|
void check_frontends(struct octoserve *os)
|
|
{
|
|
int i;
|
|
struct dvbfe *fe;
|
|
uint16_t sig, snr;
|
|
fe_status_t stat;
|
|
|
|
for (i = 0; i < os->dvbfe_num; i++) {
|
|
fe = &os->dvbfe[i];
|
|
if (fe->state != 1)
|
|
continue;
|
|
if (ioctl(fe->fd, FE_READ_STATUS, &stat) < 0)
|
|
continue;
|
|
if (ioctl(fe->fd, FE_READ_SIGNAL_STRENGTH, &sig) < 0)
|
|
continue;
|
|
if (ioctl(fe->fd, FE_READ_SNR, &snr) < 0)
|
|
continue;
|
|
fe->stat = stat;
|
|
fe->lock = (stat == 0x1f) ? 1 : 0;
|
|
fe->level = sig >> 8;
|
|
fe->quality = snr >> 12;
|
|
dbgprintf(DEBUG_DVB, "fe%d: stat=%02x str=%04x snr=%04x\n", fe->nr, stat, sig, snr);
|
|
}
|
|
}
|
|
|
|
static int check_streams(struct octoserve *os)
|
|
{
|
|
int i;
|
|
struct osstrm *str;
|
|
|
|
for (i = 0; i < MAX_STREAM; i++) {
|
|
str = &os->stream[i];
|
|
if (!str->state)
|
|
continue;
|
|
set_rtcp_msgs(str);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int alloc_igmp_socket(struct octoserve *os)
|
|
{
|
|
struct ip_mreq imr;
|
|
struct sockaddr_in sadr;
|
|
uint8_t one = 1;
|
|
|
|
os->igmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
|
|
|
|
set_nonblock(os->igmp_sock);
|
|
get_ifa(os->ifname, AF_INET, (struct sockaddr *) &sadr);
|
|
imr.imr_interface.s_addr = sadr.sin_addr.s_addr;
|
|
imr.imr_multiaddr.s_addr = inet_addr("224.0.0.22");
|
|
setsockopt(os->igmp_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
|
&imr, sizeof(imr));
|
|
setsockopt(os->igmp_sock, IPPROTO_IP, IP_MULTICAST_TTL,
|
|
&one, sizeof(one));
|
|
setsockopt(os->igmp_sock, IPPROTO_IP, IP_ROUTER_ALERT,
|
|
&one, sizeof(one));
|
|
mtime(&os->igmp_time);
|
|
/* first query after 125-94=31 seconds */
|
|
os->igmp_mode = 0;
|
|
os->igmp_time -= 94;
|
|
os->igmp_robust = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int alloc_igmp_raw_socket(struct octoserve *os)
|
|
{
|
|
struct ifreq ifr;
|
|
int s, n, i;
|
|
struct sockaddr cadr;
|
|
socklen_t len = sizeof(cadr);
|
|
uint8_t buf[4096];
|
|
struct sock_filter bpf_igmp[] = {
|
|
{ 0x28, 0, 0, 0x0000000c },
|
|
{ 0x15, 0, 3, 0x00000800 },
|
|
{ 0x30, 0, 0, 0x00000017 },
|
|
{ 0x15, 0, 1, 0x00000002 },
|
|
//{ 0x6, 0, 0, 0x0000ffff },
|
|
{ 0x6, 0, 0, 0x00040000 },
|
|
{ 0x6, 0, 0, 0x00000000 },
|
|
};
|
|
struct sock_fprog sfprog;
|
|
|
|
sfprog.len = ARRAY_SIZE(bpf_igmp);
|
|
sfprog.filter = bpf_igmp;
|
|
|
|
s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
|
|
set_nonblock(s);
|
|
|
|
memset (&ifr, 0, sizeof(struct ifreq));
|
|
strncpy(ifr.ifr_name, os->ifname, IFNAMSIZ);
|
|
ioctl(s, SIOCGIFFLAGS, &ifr);
|
|
ifr.ifr_flags |= IFF_PROMISC;
|
|
ioctl(s, SIOCSIFFLAGS, &ifr);
|
|
|
|
n = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &sfprog, sizeof(sfprog));
|
|
if (n < 0) {
|
|
dbgprintf(DEBUG_NET, "could not set filter\n");
|
|
}
|
|
|
|
#if 0
|
|
printf("raw = %d\n", s);
|
|
for (i = 0; i < 5; i++) {
|
|
n = recvfrom(s, buf, sizeof(buf), 0, NULL, NULL);
|
|
printf("raw = %d, n = %d, errno = %d\n", s, n, errno);
|
|
dump(buf, n);
|
|
}
|
|
close (s);
|
|
#endif
|
|
os->igmp_rsock = s;
|
|
return 0;
|
|
}
|
|
|
|
static void os_serve(struct octoserve *os)
|
|
{
|
|
struct sigaction sa;
|
|
fd_set fds;
|
|
int mfd, i;
|
|
struct timeval timeout;
|
|
time_t t, u;
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGHUP, SIG_IGN);
|
|
sa.sa_handler = sigchld_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
|
|
if (sigaction(SIGCHLD, &sa, NULL) == -1)
|
|
die("Error in sigaction");
|
|
|
|
os->rtsp_sock = streamsock("554", AF_INET, &os->rtsp_sadr);
|
|
sockname((struct sockaddr *) &os->rtsp_sadr, os->rtsp_ip);
|
|
dbgprintf(DEBUG_SYS, "rtsp_ip: %s, %d\n", os->rtsp_ip, os->rtsp_sock);
|
|
if (listen(os->rtsp_sock, 20) < 0) {
|
|
dbgprintf(DEBUG_SYS, "listen error");
|
|
return;
|
|
}
|
|
set_nonblock(os->rtsp_sock);
|
|
|
|
alloc_igmp_raw_socket(os);
|
|
alloc_igmp_socket(os);
|
|
|
|
#if 0
|
|
{
|
|
struct ipv6_mreq imr;
|
|
|
|
memset(&imr, 0, sizeof(imr));
|
|
imr.ipv6mr_interface = if_nametoindex(os->ifname);
|
|
inet_pton(AF_INET6, "FF02::16", &imr.ipv6mr_multiaddr);
|
|
setsockopt(os->mld_sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
|
|
&imr, sizeof(imr));
|
|
}
|
|
#endif
|
|
mtime(&t);
|
|
while (!os->exit) {
|
|
int csock, ncon;
|
|
struct sockaddr cadr;
|
|
socklen_t clen = sizeof(cadr);
|
|
uint8_t buf[2048];
|
|
int num, n;
|
|
|
|
mtime(&u);
|
|
if (u > t) {
|
|
t = u;
|
|
check_session_timeouts(os);
|
|
//check_frontends(os);
|
|
check_streams(os);
|
|
check_igmp(os);
|
|
}
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 500000;
|
|
|
|
FD_ZERO(&fds);
|
|
mfd = 0;
|
|
add_fd(os->rtsp_sock, &mfd, &fds);
|
|
//add_fd(os->igmp_sock, &mfd, &fds);
|
|
add_fd(os->igmp_rsock, &mfd, &fds);
|
|
//add_fd(os->mld_sock, &mfd, &fds);
|
|
for (i = 0, ncon = 0; i < MAX_CONNECT; i++) {
|
|
if (os->con[i].state) {
|
|
ncon++;
|
|
add_fd(os->con[i].sock, &mfd, &fds);
|
|
}
|
|
}
|
|
//printf("serve_loop: %d cons, mfd=%d\n", ncon, mfd);
|
|
num = select(mfd + 1, &fds, NULL, NULL, &timeout);
|
|
if (num < 0)
|
|
break;
|
|
#if 0
|
|
if (FD_ISSET(os->igmp_sock, &fds)) {
|
|
n = recvfrom(os->igmp_sock, buf, sizeof(buf), 0, &cadr, &clen);
|
|
if (n > 0)
|
|
proc_igmp(os, buf, n, NULL);
|
|
}
|
|
#endif
|
|
if (FD_ISSET(os->igmp_rsock, &fds)) {
|
|
n = recvfrom(os->igmp_rsock, buf, sizeof(buf), 0, &cadr, &clen);
|
|
if (n > 14)
|
|
proc_igmp(os, buf + 14, n - 14, buf);
|
|
}
|
|
if (FD_ISSET(os->mld_sock, &fds)) {
|
|
n = recvfrom(os->mld_sock, buf, sizeof(buf), 0, &cadr, &clen);
|
|
//printf("n = %d\n", n);
|
|
if (n > 0)
|
|
;//dump(buf, n);
|
|
}
|
|
#ifndef MULTI_THREADED
|
|
for (i = 0; i < MAX_CONNECT; i++)
|
|
if (os->con[i].state &&
|
|
FD_ISSET(os->con[i].sock, &fds))
|
|
con_loop(&os->con[i]);
|
|
#endif
|
|
con_check(os);
|
|
|
|
|
|
if (FD_ISSET(os->rtsp_sock, &fds)) {
|
|
struct oscon *con;
|
|
|
|
csock = accept(os->rtsp_sock, &cadr, &clen);
|
|
if (csock < 0) {
|
|
dbgprintf(DEBUG_SYS, "Could not accept\n");
|
|
continue;
|
|
}
|
|
con = alloc_con(os);
|
|
if (!con) {
|
|
dbgprintf(DEBUG_SYS, "Could not alloc new connection\n");
|
|
continue;
|
|
}
|
|
con->sock = csock;
|
|
con->trans.family = AF_INET;
|
|
init_con(con);
|
|
dbgprintf(DEBUG_SYS, "sock %d con %d on %s\n",
|
|
con->sock, con->nr, con->cadr_ip);
|
|
#ifdef MULTI_THREADED
|
|
pthread_create(&con->pt, NULL, (void *) handle_con, con);
|
|
set_block(csock);
|
|
#else
|
|
set_nonblock(csock);
|
|
#endif
|
|
}
|
|
}
|
|
killall_sessions(os);
|
|
}
|
|
|
|
static int fexists(char *fn)
|
|
{
|
|
struct stat b;
|
|
|
|
return (!stat(fn, &b));
|
|
}
|
|
|
|
static int read_num(char *fn, int *num)
|
|
{
|
|
FILE *f;
|
|
|
|
f = fopen(fn, "r");
|
|
if (!f)
|
|
return -1;
|
|
fscanf(f, "%i", num);
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
static void read_first_ds(struct octoserve *os)
|
|
{
|
|
int num;
|
|
int res = read_num("/config/first_ds", &num);
|
|
|
|
if (res < 0)
|
|
os->first_ds = 0;
|
|
else
|
|
os->first_ds = num;
|
|
|
|
dbgprintf(DEBUG_SYS, "first_ds = %u\n", os->first_ds);
|
|
}
|
|
|
|
static void read_delsys_mask(struct octoserve *os)
|
|
{
|
|
int fd, len;
|
|
char mask[80];
|
|
|
|
os->delsys_mask = 0;
|
|
fd = open("/config/delsys_mask", O_RDONLY);
|
|
if (fd < 0)
|
|
return;
|
|
len = read(fd, mask, 79);
|
|
if (len < 0)
|
|
return;
|
|
close (fd);
|
|
if (len)
|
|
os->delsys_mask = strtol(mask, NULL, 0);
|
|
dbgprintf(DEBUG_SYS, "delsys_mask = %08x\n", os->delsys_mask);
|
|
}
|
|
|
|
static int read_msmode(char *fn)
|
|
{
|
|
int fd, len;
|
|
char mode[80];
|
|
|
|
fd = open(fn, O_RDONLY);
|
|
if (fd < 0) {
|
|
if (fexists("/config/noswitch.enabled"))
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
len = read(fd, mode, 7);
|
|
if (len < 0)
|
|
return 0;
|
|
close (fd);
|
|
if (len == 4 && !strncasecmp(mode, "none", 4))
|
|
return 0;
|
|
if (len == 4 && !strncasecmp(mode, "quad", 4))
|
|
return 1;
|
|
if (len == 7 && !strncasecmp(mode, "quattro", 7))
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
static struct octoserve *os_init(char *ifname, int nossdp, int nodms)
|
|
{
|
|
struct octoserve *os;
|
|
struct os_ssdp *ss;
|
|
struct ifreq ifr;
|
|
int s;
|
|
pthread_mutexattr_t mta;
|
|
|
|
os = malloc(sizeof(*os));
|
|
if (!os)
|
|
return NULL;
|
|
dbgprintf(DEBUG_SYS, "allocated octoserve struct, %d bytes\n", sizeof(*os));
|
|
memset(os, 0, sizeof(struct octoserve));
|
|
|
|
if (fexists("/config/nodvbt.enabled"))
|
|
os->nodvbt = 1;
|
|
os->msmode = read_msmode("/config/msmode");
|
|
read_first_ds(os);
|
|
read_delsys_mask(os);
|
|
pthread_mutexattr_init(&mta);
|
|
pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init(&os->lock, &mta);
|
|
|
|
os->ifname = ifname;
|
|
os->sessionid = random();
|
|
if (get_ifa(ifname, AF_INET, (struct sockaddr *) &os->ssdp.sadr) < 0) {
|
|
perror("no such interface:");
|
|
free(os);
|
|
return NULL;
|
|
}
|
|
get_ifa(ifname, AF_INET6, (struct sockaddr *) &os->ssdp.sadr6);
|
|
sockname((struct sockaddr *) &os->ssdp.sadr, os->ssdp.ip);
|
|
sockname((struct sockaddr *) &os->ssdp.sadr6, os->ssdp.ip6);
|
|
|
|
strcpy(ifr.ifr_name, os->ifname);
|
|
s = socket(AF_INET, SOCK_DGRAM, 0);
|
|
ioctl(s, SIOCGIFHWADDR, &ifr);
|
|
close(s);
|
|
memcpy(os->mac, ifr.ifr_hwaddr.sa_data, 6);
|
|
dbgprintf(DEBUG_NET, "MAC %s=%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
os->ifname,
|
|
os->mac[0], os->mac[1], os->mac[2],
|
|
os->mac[3], os->mac[4], os->mac[5]);
|
|
if (os->has_switch)
|
|
switch_get_port(os->mac);
|
|
|
|
switch_set_entry(os->mac, 0x20, 0x08);
|
|
init_dvb(os);
|
|
|
|
ss = &os->ssdp;
|
|
if (init_ssdp(os, &os->ssdp, debug, nossdp, nodms) < 0) {
|
|
release_dvb(os);
|
|
free(os);
|
|
return NULL;
|
|
}
|
|
#if 0
|
|
if (init_httpos, debug) < 0) {
|
|
release_dvb(os);
|
|
free(os);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return os;
|
|
}
|
|
|
|
struct octoserve *os;
|
|
|
|
void term_action(int sig)
|
|
{
|
|
dbgprintf(DEBUG_SYS, "EXIT\n");
|
|
os->exit = 1;
|
|
}
|
|
|
|
static int set_termaction(void)
|
|
{
|
|
struct sigaction term;
|
|
|
|
memset(&term, 0, sizeof(term));
|
|
term.sa_handler = term_action;
|
|
sigemptyset(&term.sa_mask);
|
|
term.sa_flags = 0;
|
|
sigaction(SIGINT, &term, NULL);
|
|
|
|
memset(&term, 0, sizeof(term));
|
|
term.sa_handler = term_action;
|
|
sigemptyset(&term.sa_mask);
|
|
term.sa_flags = 0;
|
|
sigaction(SIGTERM, &term, NULL);
|
|
}
|
|
|
|
static void awrite(char *fn, char *txt)
|
|
{
|
|
FILE *f = fopen(fn, "w");
|
|
|
|
if (f)
|
|
fprintf(f, "%s", txt);
|
|
fclose(f);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int nodms = 0, nossdp = 0, vlan = 0;
|
|
|
|
printf("Octoserve " OCTOSERVE_VERSION
|
|
", Copyright (C) 2012-15 Digital Devices GmbH\n");
|
|
debug = 0;
|
|
flags = 0;
|
|
while (1) {
|
|
int option_index = 0;
|
|
int c;
|
|
static struct option long_options[] = {
|
|
{"debug", required_argument, 0, 'd'},
|
|
{"flags", required_argument, 0, 'f'},
|
|
{"nossdp", no_argument, 0, 'n'},
|
|
{"nodms", no_argument, 0, 'm'},
|
|
{"conform", no_argument, 0, 'c'},
|
|
{"help", no_argument , 0, 'h'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
c = getopt_long(argc, argv,
|
|
"d:f:nmch",
|
|
long_options, &option_index);
|
|
if (c==-1)
|
|
break;
|
|
|
|
switch (c) {
|
|
case 'd':
|
|
debug = strtoul(optarg, NULL, 16);
|
|
break;
|
|
case 'f':
|
|
flags = strtoul(optarg, NULL, 16);
|
|
break;
|
|
case 'n':
|
|
nossdp = 1;
|
|
break;
|
|
case 'm':
|
|
nodms = 1;
|
|
break;
|
|
case 'c':
|
|
conform = 1;
|
|
break;
|
|
case 'h':
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
if (optind < argc) {
|
|
dbgprintf(DEBUG_SYS, "Warning: unused arguments\n");
|
|
}
|
|
if (fexists("/config/nodms.enabled"))
|
|
nodms = 1;
|
|
if (fexists("/config/vlan.enabled")) {
|
|
awrite("/sys/class/ddbridge/ddbridge0/vlan", "1");
|
|
vlan = 1;
|
|
} else
|
|
awrite("/sys/class/ddbridge/ddbridge0/vlan", "0");
|
|
dbgprintf(DEBUG_SYS, "nodms = %d, vlan = %d\n", nodms, vlan);
|
|
|
|
os = os_init("eth0", nossdp, nodms);
|
|
if (!os)
|
|
return -1;
|
|
set_termaction();
|
|
|
|
os->has_switch = switch_test();
|
|
if (os->has_switch)
|
|
dbgprintf(DEBUG_SYS, "Switch detected\n");
|
|
else
|
|
dbgprintf(DEBUG_SYS, "No switch detected\n");
|
|
|
|
os_serve(os);
|
|
if (!nossdp)
|
|
pthread_join(os->ssdp.pt, NULL);
|
|
release_dvb(os);
|
|
free(os);
|
|
}
|