Updated for vdr-1.7.30 and added support for source-specific multicasts (SSM).

Changed default external script directory from the configuration to the resource.
This commit is contained in:
Rolf Ahrenberg 2012-09-30 18:42:40 +03:00
parent 75d17b289b
commit 11d864c16b
11 changed files with 204 additions and 99 deletions

View File

@ -181,3 +181,10 @@ VDR Plugin 'iptv' Revision History
2012-07-10: Version 1.0.1 2012-07-10: Version 1.0.1
- Added FreeBSD support (Thanks to Jürgen Lock). - Added FreeBSD support (Thanks to Jürgen Lock).
2012-09-30: Version 1.1.0
- Updated for vdr-1.7.30.
- Added support for source-specific multicasts (SSM).
- Changed default external script directory from the
configuration to the resource.

18
README
View File

@ -44,7 +44,7 @@ cd /put/your/path/here/VDR/PLUGINS/src
tar -xzf /put/your/path/here/vdr-iptv-X.Y.Z.tgz tar -xzf /put/your/path/here/vdr-iptv-X.Y.Z.tgz
ln -s iptv-X.Y.Z iptv ln -s iptv-X.Y.Z iptv
cd /put/your/path/here/VDR cd /put/your/path/here/VDR
cp -R PLUGINS/src/iptv/iptv /path/to/vdrconf/plugins/ cp -R PLUGINS/src/iptv/iptv /path/to/vdrresource/plugins/
make make
make plugins make plugins
./vdr -P iptv ./vdr -P iptv
@ -91,14 +91,15 @@ Configuration:
TV4;IPTV:40:S=1|P=0|F=EXT|U=iptvstream.sh|A=0:I:0:0:680:0:0:4:0:0:0 TV4;IPTV:40:S=1|P=0|F=EXT|U=iptvstream.sh|A=0:I:0:0:680:0:0:4:0:0:0
TV3;IPTV:30:S=0|P=1|F=FILE|U=/video/stream.ts|A=5:I:0:514:670:2321:0:3:0:0:0 TV3;IPTV:30:S=0|P=1|F=FILE|U=/video/stream.ts|A=5:I:0:514:670:2321:0:3:0:0:0
TV2;IPTV:20:S=0|P=1|F=HTTP|U=127.0.0.1/TS/2|A=3000:I:0:513:660:2321:0:2:0:0:0 TV2;IPTV:20:S=0|P=1|F=HTTP|U=127.0.0.1/TS/2|A=3000:I:0:513:660:2321:0:2:0:0:0
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1@127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0 TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | Source type ("I") | | | | | | Source type ("I")
| | | | | Stream parameter (multicast port | | | | | Stream parameter (multicast port
| | | | | number, HTTP port number, file delay | | | | | number, HTTP port number, file delay
| | | | | (ms), script parameter) | | | | | (ms), script parameter)
| | | | Stream address (multicast address, URL, file | | | | Stream address (multicast source@group address,
| | | | location, script location) | | | | URL, file location, script location)
| | | Stream protocol ("UDP", "HTTP", "FILE", "EXT") | | | Stream protocol ("UDP", "HTTP", "FILE", "EXT")
| | Pid scanner ("0" disable, "1" enable) | | Pid scanner ("0" disable, "1" enable)
| Section id (Sid/Nid/Tid) scanner ("0" disable, "1" enable) | Section id (Sid/Nid/Tid) scanner ("0" disable, "1" enable)
@ -119,9 +120,9 @@ External streaming:
- To watch an externally received channel add an EXT entry to channels.conf - To watch an externally received channel add an EXT entry to channels.conf
and specify a script name and parameter. The specified script is executed and specify a script name and parameter. The specified script is executed
from plugin configuration directory when VDR tunes to the channel. The from plugin resource directory when VDR tunes to the channel. The specified
specified script parameter is passed to the script and it can be used to script parameter is passed to the script and it can be used to select for
select for example between different URLs. example between different URLs.
- When an EXT channel is opened the IPTV plugin opens an UDP listening port - When an EXT channel is opened the IPTV plugin opens an UDP listening port
on the localhost. The external script is responsible for supplying IPTV on the localhost. The external script is responsible for supplying IPTV
@ -167,6 +168,11 @@ Notes:
- Section id and pid scanners should be disabled after the correct data is - Section id and pid scanners should be disabled after the correct data is
found. This can be made via VDR's channel editor. found. This can be made via VDR's channel editor.
- Source-specific multicast (SSM) can be enabled by defining both the source
address and the group address separated by a '@' character. This will use
IGMP v3 protocol:
"U=<source address>@<group address>"
Acknowledgements: Acknowledgements:
- The IPTV section filtering code is derived from Linux kernel. - The IPTV section filtering code is derived from Linux kernel.

View File

@ -343,26 +343,37 @@ int cIptvDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) { for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
if (!secfilters[i]) { if (!secfilters[i]) {
//debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i); //debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i);
secfilters[i] = new cIptvSectionFilter(deviceIndex, i, Pid, Tid, Mask); secfilters[i] = new cIptvSectionFilter(deviceIndex, Pid, Tid, Mask);
return secfilters[i]->GetReadDesc(); if (secfilters[i])
return i;
break;
} }
} }
// No free filter slot found // No free filter slot found
return -1; return -1;
} }
int cIptvDevice::ReadFilter(int Handle, void *Buffer, size_t Length)
{
// Lock
cMutexLock MutexLock(&mutex);
// ... and load
if (secfilters[Handle]) {
return secfilters[Handle]->Read(Buffer, Length);
//debug("cIptvDevice::ReadFilter(%d): %d %d\n", deviceIndex, Handle, Length);
}
return 0;
}
void cIptvDevice::CloseFilter(int Handle) void cIptvDevice::CloseFilter(int Handle)
{ {
// Lock // Lock
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
// Search the filter for deletion // ... and load
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) { if (secfilters[Handle]) {
if (secfilters[i] && (Handle == secfilters[i]->GetReadDesc())) { //debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle);
//debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle); DeleteFilter(Handle);
DeleteFilter(i); }
break;
}
}
} }
bool cIptvDevice::OpenDvr(void) bool cIptvDevice::OpenDvr(void)
@ -395,6 +406,12 @@ bool cIptvDevice::HasLock(int TimeoutMs)
return (!IsBuffering()); return (!IsBuffering());
} }
bool cIptvDevice::HasInternalCam(void)
{
//debug("cIptvDevice::HasInternalCam(%d)\n", deviceIndex);
return true;
}
void cIptvDevice::ResetBuffering(void) void cIptvDevice::ResetBuffering(void)
{ {
debug("cIptvDevice::ResetBuffering(%d)\n", deviceIndex); debug("cIptvDevice::ResetBuffering(%d)\n", deviceIndex);

View File

@ -76,6 +76,7 @@ private:
bool IsBlackListed(u_short Pid, u_char Tid, u_char Mask) const; bool IsBlackListed(u_short Pid, u_char Tid, u_char Mask) const;
// for channel info // for channel info
public:
virtual cString DeviceType(void) const; virtual cString DeviceType(void) const;
virtual cString DeviceName(void) const; virtual cString DeviceName(void) const;
virtual int SignalStrength(void) const; virtual int SignalStrength(void) const;
@ -101,11 +102,16 @@ protected:
// for section filtering // for section filtering
public: public:
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask); virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
virtual int ReadFilter(int Handle, void *Buffer, size_t Length);
virtual void CloseFilter(int Handle); virtual void CloseFilter(int Handle);
// for transponder lock // for transponder lock
public: public:
virtual bool HasLock(int); virtual bool HasLock(int);
// for common interface
public:
virtual bool HasInternalCam(void);
}; };
#endif // __IPTV_DEVICE_H #endif // __IPTV_DEVICE_H

8
iptv.c
View File

@ -13,15 +13,15 @@
#include "device.h" #include "device.h"
#include "iptvservice.h" #include "iptvservice.h"
#if defined(APIVERSNUM) && APIVERSNUM < 10727 #if defined(APIVERSNUM) && APIVERSNUM < 10730
#error "VDR-1.7.27 API version or greater is required!" #error "VDR-1.7.30 API version or greater is required!"
#endif #endif
#ifndef GITVERSION #ifndef GITVERSION
#define GITVERSION "" #define GITVERSION ""
#endif #endif
const char VERSION[] = "1.0.1" GITVERSION; const char VERSION[] = "1.1.0" GITVERSION;
static const char DESCRIPTION[] = trNOOP("Experience the IPTV"); static const char DESCRIPTION[] = trNOOP("Experience the IPTV");
class cPluginIptv : public cPlugin { class cPluginIptv : public cPlugin {
@ -99,7 +99,7 @@ bool cPluginIptv::Initialize(void)
{ {
debug("cPluginIptv::Initialize()\n"); debug("cPluginIptv::Initialize()\n");
// Initialize any background activities the plugin shall perform. // Initialize any background activities the plugin shall perform.
IptvConfig.SetConfigDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N)); IptvConfig.SetConfigDirectory(cPlugin::ResourceDirectory(PLUGIN_NAME_I18N));
return cIptvDevice::Initialize(deviceCount); return cIptvDevice::Initialize(deviceCount);
} }

View File

@ -19,7 +19,9 @@
#include "socket.h" #include "socket.h"
cIptvProtocolUdp::cIptvProtocolUdp() cIptvProtocolUdp::cIptvProtocolUdp()
: streamAddr(strdup("")), : isIGMPv3(false),
sourceAddr(strdup("")),
streamAddr(strdup("")),
streamPort(0) streamPort(0)
{ {
debug("cIptvProtocolUdp::cIptvProtocolUdp()\n"); debug("cIptvProtocolUdp::cIptvProtocolUdp()\n");
@ -32,12 +34,13 @@ cIptvProtocolUdp::~cIptvProtocolUdp()
cIptvProtocolUdp::Close(); cIptvProtocolUdp::Close();
// Free allocated memory // Free allocated memory
free(streamAddr); free(streamAddr);
free(sourceAddr);
} }
bool cIptvProtocolUdp::Open(void) bool cIptvProtocolUdp::Open(void)
{ {
debug("cIptvProtocolUdp::Open(): streamAddr=%s\n", streamAddr); debug("cIptvProtocolUdp::Open(): streamAddr=%s\n", streamAddr);
OpenSocket(streamPort, inet_addr(streamAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
// Join a new multicast group // Join a new multicast group
JoinMulticast(); JoinMulticast();
@ -50,12 +53,13 @@ bool cIptvProtocolUdp::Close(void)
debug("cIptvProtocolUdp::Close(): streamAddr=%s\n", streamAddr); debug("cIptvProtocolUdp::Close(): streamAddr=%s\n", streamAddr);
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
// Drop the multicast group // Drop the multicast group
OpenSocket(streamPort, inet_addr(streamAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
DropMulticast(); DropMulticast();
} }
// Close the socket // Close the socket
CloseSocket(); CloseSocket();
// Do NOT reset stream and source addresses // Do NOT reset stream and source addresses
//sourceAddr = strcpyrealloc(sourceAddr, "");
//streamAddr = strcpyrealloc(streamAddr, ""); //streamAddr = strcpyrealloc(streamAddr, "");
//streamPort = 0; //streamPort = 0;
return true; return true;
@ -72,15 +76,27 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
if (!isempty(Location)) { if (!isempty(Location)) {
// Drop the multicast group // Drop the multicast group
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
OpenSocket(streamPort, inet_addr(streamAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
DropMulticast(); DropMulticast();
} }
// Update stream address and port // Update stream address and port
streamAddr = strcpyrealloc(streamAddr, Location); streamAddr = strcpyrealloc(streamAddr, Location);
// <group address> or <source address>@<group address>
char *p = strstr(streamAddr, "@");
if (p) {
*p = 0;
sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
streamAddr = strcpyrealloc(streamAddr, p + 1);
isIGMPv3 = true;
}
else {
sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
isIGMPv3 = false;
}
streamPort = Parameter; streamPort = Parameter;
// Join a new multicast group // Join a new multicast group
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
OpenSocket(streamPort, inet_addr(streamAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
JoinMulticast(); JoinMulticast();
} }
} }
@ -90,5 +106,7 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
cString cIptvProtocolUdp::GetInformation(void) cString cIptvProtocolUdp::GetInformation(void)
{ {
//debug("cIptvProtocolUdp::GetInformation()"); //debug("cIptvProtocolUdp::GetInformation()");
if (isIGMPv3)
return cString::sprintf("udp://%s@%s:%d", sourceAddr, streamAddr, streamPort);
return cString::sprintf("udp://%s:%d", streamAddr, streamPort); return cString::sprintf("udp://%s:%d", streamAddr, streamPort);
} }

View File

@ -14,6 +14,8 @@
class cIptvProtocolUdp : public cIptvUdpSocket, public cIptvProtocolIf { class cIptvProtocolUdp : public cIptvUdpSocket, public cIptvProtocolIf {
private: private:
bool isIGMPv3;
char* sourceAddr;
char* streamAddr; char* streamAddr;
int streamPort; int streamPort;

View File

@ -5,10 +5,10 @@
* *
*/ */
#include "config.h"
#include "sectionfilter.h" #include "sectionfilter.h"
cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index, cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask)
uint16_t Pid, uint8_t Tid, uint8_t Mask)
: pusi_seen(0), : pusi_seen(0),
feedcc(0), feedcc(0),
doneq(0), doneq(0),
@ -17,10 +17,9 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
seclen(0), seclen(0),
tsfeedp(0), tsfeedp(0),
pid(Pid), pid(Pid),
devid(DeviceIndex), devid(DeviceIndex)
id(Index)
{ {
//debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, id); //debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, pid);
int i; int i;
memset(secbuf_base, '\0', sizeof(secbuf_base)); memset(secbuf_base, '\0', sizeof(secbuf_base));
@ -47,35 +46,30 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
} }
doneq = local_doneq ? 1 : 0; doneq = local_doneq ? 1 : 0;
// Create sockets // Create filtering buffer
socket[0] = socket[1] = -1; ringbuffer = new cRingBufferLinear(KILOBYTE(128), 0, false, *cString::sprintf("IPTV SECTION %d/%d", devid, pid));
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socket) != 0) { if (ringbuffer)
char tmp[64]; ringbuffer->SetTimeouts(10, 10);
error("Opening section filter sockets failed (device=%d id=%d): %s\n", devid, id, strerror_r(errno, tmp, sizeof(tmp))); else
} error("Failed to allocate buffer for section filter (device=%d pid=%d): ", devid, pid);
else if ((fcntl(socket[0], F_SETFL, O_NONBLOCK) != 0) || (fcntl(socket[1], F_SETFL, O_NONBLOCK) != 0)) {
char tmp[64];
error("Setting section filter socket to non-blocking mode failed (device=%d id=%d): %s", devid, id, strerror_r(errno, tmp, sizeof(tmp)));
}
} }
cIptvSectionFilter::~cIptvSectionFilter() cIptvSectionFilter::~cIptvSectionFilter()
{ {
//debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, id); //debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, pid);
int tmp = socket[1]; DELETE_POINTER(ringbuffer);
socket[1] = -1;
if (tmp >= 0)
close(tmp);
tmp = socket[0];
socket[0] = -1;
if (tmp >= 0)
close(tmp);
secbuf = NULL; secbuf = NULL;
} }
int cIptvSectionFilter::GetReadDesc(void) int cIptvSectionFilter::Read(void *Data, size_t Length)
{ {
return socket[0]; int count = 0;
uchar *p = ringbuffer->Get(count);
if (p && count > 0) {
memcpy(Data, p, count);
ringbuffer->Del(count);
}
return count;
} }
inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *Data) inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *Data)
@ -105,10 +99,10 @@ int cIptvSectionFilter::Filter(void)
if (doneq && !neq) if (doneq && !neq)
return 0; return 0;
// There is no data in the read socket, more can be written if (ringbuffer) {
if ((socket[0] >= 0) && (socket[1] >= 0) /*&& !select_single_desc(socket[0], 0, false)*/) { int len = ringbuffer->Put(secbuf, seclen);
ssize_t len = write(socket[1], secbuf, seclen); if (len != seclen)
ERROR_IF(len < 0, "write()"); ringbuffer->ReportOverflow(seclen - len);
// Update statistics // Update statistics
AddSectionStatistic(len, 1); AddSectionStatistic(len, 1);
} }

View File

@ -36,8 +36,6 @@ private:
uint16_t pid; uint16_t pid;
int devid; int devid;
int id;
int socket[2];
uint8_t filter_value[DMX_MAX_FILTER_SIZE]; uint8_t filter_value[DMX_MAX_FILTER_SIZE];
uint8_t filter_mask[DMX_MAX_FILTER_SIZE]; uint8_t filter_mask[DMX_MAX_FILTER_SIZE];
@ -46,6 +44,8 @@ private:
uint8_t maskandmode[DMX_MAX_FILTER_SIZE]; uint8_t maskandmode[DMX_MAX_FILTER_SIZE];
uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE]; uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE];
cRingBufferLinear *ringbuffer;
inline uint16_t GetLength(const uint8_t *Data); inline uint16_t GetLength(const uint8_t *Data);
void New(void); void New(void);
int Filter(void); int Filter(void);
@ -54,11 +54,10 @@ private:
public: public:
// constructor & destructor // constructor & destructor
cIptvSectionFilter(int Index, int DeviceIndex, uint16_t Pid, cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask);
uint8_t Tid, uint8_t Mask);
virtual ~cIptvSectionFilter(); virtual ~cIptvSectionFilter();
void Process(const uint8_t* Data); void Process(const uint8_t* Data);
int GetReadDesc(void); int Read(void *Buffer, size_t Length);
uint16_t GetPid(void) const { return pid; } uint16_t GetPid(void) const { return pid; }
}; };

128
socket.c
View File

@ -7,6 +7,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <net/if.h>
#include <netdb.h> #include <netdb.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@ -63,7 +64,7 @@ bool cIptvSocket::OpenSocket(const int Port, const bool isUdp)
CloseSocket(), return false); CloseSocket(), return false);
#endif // __FreeBSD__ #endif // __FreeBSD__
// Bind socket // Bind socket
memset(&sockAddr, '\0', sizeof(sockAddr)); memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET; sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons((uint16_t)(Port & 0xFFFF)); sockAddr.sin_port = htons((uint16_t)(Port & 0xFFFF));
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
@ -89,9 +90,33 @@ void cIptvSocket::CloseSocket(void)
} }
} }
bool cIptvSocket::CheckAddress(const char *Addr, in_addr_t *InAddr)
{
if (InAddr) {
// First try only the IP address
*InAddr = htonl(inet_addr(Addr));
if (*InAddr == htonl(INADDR_NONE)) {
debug("Cannot convert %s directly to internet address\n", Addr);
// It may be a host name, get the name
struct hostent *host;
host = gethostbyname(Addr);
if (!host) {
char tmp[64];
error("gethostbyname() failed: %s is not valid address: %s", Addr, strerror_r(h_errno, tmp, sizeof(tmp)));
return false;
}
*InAddr = htonl(inet_addr(*host->h_addr_list));
}
return true;
}
return false;
}
// UDP socket class // UDP socket class
cIptvUdpSocket::cIptvUdpSocket() cIptvUdpSocket::cIptvUdpSocket()
: streamAddr(INADDR_ANY) : streamAddr(htonl(INADDR_ANY)),
sourceAddr(htonl(INADDR_ANY)),
useIGMPv3(false)
{ {
debug("cIptvUdpSocket::cIptvUdpSocket()\n"); debug("cIptvUdpSocket::cIptvUdpSocket()\n");
} }
@ -101,17 +126,30 @@ cIptvUdpSocket::~cIptvUdpSocket()
debug("cIptvUdpSocket::~cIptvUdpSocket()\n"); debug("cIptvUdpSocket::~cIptvUdpSocket()\n");
} }
bool cIptvUdpSocket::OpenSocket(const int Port, const in_addr_t StreamAddr) bool cIptvUdpSocket::OpenSocket(const int Port)
{ {
debug("cIptvUdpSocket::OpenSocket()\n"); debug("cIptvUdpSocket::OpenSocket()\n");
streamAddr = StreamAddr; streamAddr = htonl(INADDR_ANY);
sourceAddr = htonl(INADDR_ANY);
useIGMPv3 = false;
return cIptvSocket::OpenSocket(Port, true);
}
bool cIptvUdpSocket::OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3)
{
debug("cIptvUdpSocket::OpenSocket()\n");
CheckAddress(StreamAddr, &streamAddr);
CheckAddress(SourceAddr, &sourceAddr);
useIGMPv3 = UseIGMPv3;
return cIptvSocket::OpenSocket(Port, true); return cIptvSocket::OpenSocket(Port, true);
} }
void cIptvUdpSocket::CloseSocket(void) void cIptvUdpSocket::CloseSocket(void)
{ {
debug("cIptvUdpSocket::CloseSocket()\n"); debug("cIptvUdpSocket::CloseSocket()\n");
streamAddr = INADDR_ANY; streamAddr = htonl(INADDR_ANY);
sourceAddr = htonl(INADDR_ANY);
useIGMPv3 = false;
cIptvSocket::CloseSocket(); cIptvSocket::CloseSocket();
} }
@ -121,10 +159,28 @@ bool cIptvUdpSocket::JoinMulticast(void)
// Check if socket exists // Check if socket exists
if (!isActive && (socketDesc >= 0)) { if (!isActive && (socketDesc >= 0)) {
// Join a new multicast group // Join a new multicast group
struct ip_mreq mreq; if (useIGMPv3) {
mreq.imr_multiaddr.s_addr = streamAddr; // Source-specific multicast (SSM) is used
mreq.imr_interface.s_addr = htonl(INADDR_ANY); struct group_source_req gsr;
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false); struct sockaddr_in *grp;
struct sockaddr_in *src;
gsr.gsr_interface = 0; // if_nametoindex("any") ?
grp = (struct sockaddr_in*)&gsr.gsr_group;
grp->sin_family = AF_INET;
grp->sin_addr.s_addr = streamAddr;
grp->sin_port = 0;
src = (struct sockaddr_in*)&gsr.gsr_source;
src->sin_family = AF_INET;
src->sin_addr.s_addr = sourceAddr;
src->sin_port = 0;
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_JOIN_SOURCE_GROUP)", return false);
}
else {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = streamAddr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false);
}
// Update multicasting flag // Update multicasting flag
isActive = true; isActive = true;
} }
@ -137,10 +193,28 @@ bool cIptvUdpSocket::DropMulticast(void)
// Check if socket exists // Check if socket exists
if (isActive && (socketDesc >= 0)) { if (isActive && (socketDesc >= 0)) {
// Drop the existing multicast group // Drop the existing multicast group
struct ip_mreq mreq; if (useIGMPv3) {
mreq.imr_multiaddr.s_addr = streamAddr; // Source-specific multicast (SSM) is used
mreq.imr_interface.s_addr = htonl(INADDR_ANY); struct group_source_req gsr;
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false); struct sockaddr_in *grp;
struct sockaddr_in *src;
gsr.gsr_interface = 0; // if_nametoindex("any") ?
grp = (struct sockaddr_in*)&gsr.gsr_group;
grp->sin_family = AF_INET;
grp->sin_addr.s_addr = streamAddr;
grp->sin_port = 0;
src = (struct sockaddr_in*)&gsr.gsr_source;
src->sin_family = AF_INET;
src->sin_addr.s_addr = sourceAddr;
src->sin_port = 0;
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_LEAVE_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_LEAVE_SOURCE_GROUP)", return false);
}
else {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = streamAddr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false);
}
// Update multicasting flag // Update multicasting flag
isActive = false; isActive = false;
} }
@ -161,7 +235,6 @@ int cIptvUdpSocket::Read(unsigned char* BufferAddr, unsigned int BufferLen)
do { do {
socklen_t addrlen = sizeof(sockAddr); socklen_t addrlen = sizeof(sockAddr);
struct msghdr msgh; struct msghdr msgh;
struct cmsghdr *cmsg;
struct iovec iov; struct iovec iov;
char cbuf[256]; char cbuf[256];
len = 0; len = 0;
@ -184,10 +257,10 @@ int cIptvUdpSocket::Read(unsigned char* BufferAddr, unsigned int BufferLen)
if (len > 0) { if (len > 0) {
#ifndef __FreeBSD__ #ifndef __FreeBSD__
// Process auxiliary received data and validate source address // Process auxiliary received data and validate source address
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) { if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg); struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg);
if ((i->ipi_addr.s_addr == streamAddr) || (INADDR_ANY == streamAddr)) { if ((i->ipi_addr.s_addr == streamAddr) || (htonl(INADDR_ANY) == streamAddr)) {
#endif // __FreeBSD__ #endif // __FreeBSD__
if (BufferAddr[0] == TS_SYNC_BYTE) if (BufferAddr[0] == TS_SYNC_BYTE)
return len; return len;
@ -245,29 +318,8 @@ cIptvTcpSocket::~cIptvTcpSocket()
bool cIptvTcpSocket::OpenSocket(const int Port, const char *StreamAddr) bool cIptvTcpSocket::OpenSocket(const int Port, const char *StreamAddr)
{ {
debug("cIptvTcpSocket::OpenSocket()\n"); debug("cIptvTcpSocket::OpenSocket()\n");
// Socket must be opened before setting the host address // Socket must be opened before setting the host address
bool retval = cIptvSocket::OpenSocket(Port, false); return (cIptvSocket::OpenSocket(Port, false) && CheckAddress(StreamAddr, &sockAddr.sin_addr.s_addr));
// First try only the IP address
sockAddr.sin_addr.s_addr = inet_addr(StreamAddr);
if (sockAddr.sin_addr.s_addr == INADDR_NONE) {
debug("Cannot convert %s directly to internet address\n", StreamAddr);
// It may be a host name, get the name
struct hostent *host;
host = gethostbyname(StreamAddr);
if (!host) {
char tmp[64];
error("gethostbyname() failed: %s is not valid address: %s", StreamAddr, strerror_r(h_errno, tmp, sizeof(tmp)));
return false;
}
sockAddr.sin_addr.s_addr = inet_addr(*host->h_addr_list);
}
return retval;
} }
void cIptvTcpSocket::CloseSocket(void) void cIptvTcpSocket::CloseSocket(void)

View File

@ -25,6 +25,7 @@ protected:
protected: protected:
bool OpenSocket(const int Port, const bool isUdp); bool OpenSocket(const int Port, const bool isUdp);
void CloseSocket(void); void CloseSocket(void);
bool CheckAddress(const char *Addr, in_addr_t *InAddr);
public: public:
cIptvSocket(); cIptvSocket();
@ -34,12 +35,15 @@ public:
class cIptvUdpSocket : public cIptvSocket { class cIptvUdpSocket : public cIptvSocket {
private: private:
in_addr_t streamAddr; in_addr_t streamAddr;
in_addr_t sourceAddr;
bool useIGMPv3;
public: public:
cIptvUdpSocket(); cIptvUdpSocket();
virtual ~cIptvUdpSocket(); virtual ~cIptvUdpSocket();
virtual int Read(unsigned char* BufferAddr, unsigned int BufferLen); virtual int Read(unsigned char* BufferAddr, unsigned int BufferLen);
bool OpenSocket(const int Port, const in_addr_t StreamAddr = INADDR_ANY); bool OpenSocket(const int Port);
bool OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3);
void CloseSocket(void); void CloseSocket(void);
bool JoinMulticast(void); bool JoinMulticast(void);
bool DropMulticast(void); bool DropMulticast(void);