summaryrefslogtreecommitdiff
path: root/mcast/netcv2dvbip/igmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'mcast/netcv2dvbip/igmp.c')
-rw-r--r--mcast/netcv2dvbip/igmp.c890
1 files changed, 890 insertions, 0 deletions
diff --git a/mcast/netcv2dvbip/igmp.c b/mcast/netcv2dvbip/igmp.c
new file mode 100644
index 0000000..45b0e60
--- /dev/null
+++ b/mcast/netcv2dvbip/igmp.c
@@ -0,0 +1,890 @@
+#include "defs.h"
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <winsock2.h>
+#ifndef __MINGW32__
+#include <mstcpip.h>
+#else
+#define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VENDOR,3)
+#endif
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#ifdef APPLE
+#include <netinet/ip_mroute.h>
+#else
+#define _LINUX_IN_H
+#include <linux/mroute.h>
+#endif
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "igmp.h"
+#include "streamer.h"
+#include "misc.h"
+
+#ifndef IGMP_ALL_HOSTS
+#define IGMP_ALL_HOSTS htonl(0xE0000001L)
+#endif
+#ifndef IGMP_ALL_ROUTER
+#define IGMP_ALL_ROUTER htonl(0xE0000002L)
+#endif
+#ifndef IGMP_ALL_V3REPORTS
+#define IGMP_ALL_V3REPORTS htonl(0xE0000016L)
+#endif
+
+// IGMP parameters according to RFC2236. All time values in seconds.
+#define IGMP_ROBUSTNESS 2
+#define IGMP_QUERY_INTERVAL 125
+#define IGMP_QUERY_RESPONSE_INTERVAL 10
+#define IGMP_GROUP_MEMBERSHIP_INTERVAL \
+ (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL)
+#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL \
+ (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2)
+#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4)
+#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS
+// This value is 1/10 sec. RFC default is 10. Reduced to minimum to
+// free unused channels ASAP
+#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1
+#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS
+
+
+// operations on struct timeval
+#define TV_CMP(a, cmp, b) \
+ (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec)
+#define TV_SET(tv) (tv.tv_sec || tv.tv_usec)
+#define TV_CLR(tv) memset(&tv, 0, sizeof(tv))
+#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst))
+#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; \
+ dst.tv_usec += (ts % 10) * 100000; \
+ if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; }
+
+/* Taken from http://tools.ietf.org/html/rfc1071 */
+uint16_t inetChecksum(uint16_t *addr, int count)
+{
+ uint32_t sum = 0;
+ while (count > 1) {
+ sum += *addr++;
+ count -= 2;
+ }
+
+ if( count > 0 )
+ sum += * (uint8_t *) addr;
+
+ while (sum>>16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+
+cMulticastGroup::cMulticastGroup(in_addr_t Group):
+ group(Group),
+ reporter(0),
+ stream(NULL)
+{
+ TV_CLR(timeout);
+ TV_CLR(v1timer);
+ TV_CLR(retransmit);
+}
+
+cIgmpListener::cIgmpListener(cIgmpMain* igmpmain)
+ : cThread("IGMP listener")
+{
+ m_IgmpMain = igmpmain;
+}
+
+bool cIgmpListener::Membership(in_addr_t mcaddr, bool Add)
+{
+ int rc = 0;
+
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = mcaddr;
+ mreq.imr_interface.s_addr = m_bindaddr;
+
+ rc = ::setsockopt(m_socket, IPPROTO_IP,
+ Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP,
+ (const char*) &mreq, sizeof(mreq));
+ if (rc < 0)
+ {
+ printf("IGMP: unable to %s %s", Add ? "join" : "leave", \
+ inet_ntoa(mreq.imr_multiaddr));
+#ifndef WIN32
+ if (errno == ENOBUFS)
+ printf("consider increasing sys.net.ipv4.igmp_max_memberships");
+ else
+#endif
+ log_socket_error("IGMP setsockopt(IP_ADD/DROP_MEMBERSHIP)");
+ return false;
+ }
+
+ return true;
+}
+
+void cIgmpListener::Destruct(void)
+{
+ int rc = 0;
+ in_addr_t defaultaddr = INADDR_ANY;
+
+ Cancel(3);
+
+ Membership(IGMP_ALL_HOSTS, false);
+ Membership(IGMP_ALL_ROUTER, false);
+ Membership(IGMP_ALL_V3REPORTS, false);
+
+ rc = setsockopt(m_socket, IPPROTO_IP, IP_MULTICAST_IF,
+ (char *)&defaultaddr, sizeof(defaultaddr));
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(IP_MULTICAST_IF)");
+ }
+
+#ifdef WIN32
+ closesocket(m_socket);
+#else
+ rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_DONE, NULL, 0);
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(MRT_DONE)");
+ }
+ close(m_socket);
+#endif
+}
+
+bool cIgmpListener::Initialize(iface_t bindif, int table)
+{
+ int rc = 0;
+#ifndef WIN32
+ int val = 0;
+#endif
+
+ m_bindaddr = bindif.ipaddr;
+
+ m_socket = ::socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
+
+ if (m_socket < 0)
+ {
+ log_socket_error("IGMP socket()");
+ return false;
+ }
+
+#ifdef WIN32
+ //-------------------------
+ // Set the socket I/O mode: In this case FIONBIO
+ // enables or disables the blocking mode for the
+ // socket based on the numerical value of iMode.
+ // If iMode = 0, blocking is enabled;
+ // If iMode != 0, non-blocking mode is enabled.
+ u_long iMode = 1;
+ ioctlsocket(m_socket, FIONBIO, &iMode);
+#else
+ int x=fcntl(m_socket,F_GETFL,0); // Get socket flags
+ fcntl(m_socket,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
+#endif
+
+
+#ifndef WIN32
+#ifdef MRT_TABLE
+ if ( table )
+ {
+ rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_TABLE, (void*)&table, sizeof(table) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(MRT_TABLE)");
+ printf("IGMPv2 policy routing won't work! Please "
+ "enable multicast policy routing option in"
+ " the kernel configuration!\n");
+ }
+ }
+#endif
+ val = 1;
+ rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_INIT,
+ (void*)&val, sizeof(val) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(MRT_INIT)");
+ printf("IGMPv2 won't work! Please enable multicast router "
+ "option in the kernel configuration!\n");
+ }
+
+ struct vifctl VifCtl;
+ VifCtl.vifc_vifi = 0;
+ VifCtl.vifc_flags = 0; // no tunnel, no source routing, register ?
+ VifCtl.vifc_threshold = 1;// Packet TTL must be at least 1 to pass them
+ VifCtl.vifc_rate_limit = 0;// Ratelimit
+
+ VifCtl.vifc_lcl_addr.s_addr = m_bindaddr;
+ VifCtl.vifc_rmt_addr.s_addr = INADDR_ANY;
+
+ rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_ADD_VIF,
+ (char *)&VifCtl, sizeof( VifCtl ) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(MRT_ADD_VIF)");
+ }
+
+
+#endif
+/*
+ rc = setsockopt(m_socket, IPPROTO_IP, IP_MULTICAST_IF,
+ (char *)&m_bindaddr, sizeof(m_bindaddr));
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(IP_MULTICAST_IF)");
+ return false;
+ }
+*/
+ /*
+ val = 1;
+ rc = ::setsockopt( m_socket, SOL_SOCKET, SO_REUSEADDR,
+ (const char*)&val, sizeof(val) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(SO_REUSEADDR)");
+ return false;
+ }
+ */
+
+ /*
+ val = 1;
+ rc = ::setsockopt( m_socket, SOL_IP, IP_ROUTER_ALERT, &val,
+ sizeof(val) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(IP_ROUTER_ALERT)");
+ return false;
+ }
+ */
+
+#ifdef WIN32
+ //----------------------
+ // Bind the socket.
+ // WIN32 requires this on a specific interface!
+ // INADDR_ANY is not allowed according to MSDN Lib for raw IGMP sockets.
+ sockaddr_in m_LocalAddr;
+
+ m_LocalAddr.sin_family = AF_INET;
+ m_LocalAddr.sin_port = 0;
+ m_LocalAddr.sin_addr.s_addr = m_bindaddr;
+
+ rc = ::bind( m_socket, (struct sockaddr*)&m_LocalAddr,
+ sizeof(m_LocalAddr) );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP bind()");
+ return false;
+ }
+
+ int len = sizeof(struct sockaddr_in);
+ rc = ::getsockname( m_socket, (struct sockaddr*)&m_LocalAddr,
+ (socklen_t*) &len );
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP getsockname()");
+ return false;
+ }
+#else
+#ifndef APPLE
+ // Normal bind() for this socket does not really work. the socket
+ // does not receive anything then.
+ // However, SO_BINDTODEVICE _does_ work. Maybe because of SOCK_RAW?!
+ rc = setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE,
+ (char *)&bindif.name, sizeof(bindif.name));
+ if ( rc < 0 )
+ {
+ log_socket_error("IGMP setsockopt(SO_BINDTODEVICE)");
+ return false;
+ }
+#endif
+#endif
+
+#ifdef WIN32
+ DWORD dwCtlCode = SIO_RCVALL_IGMPMCAST;
+ DWORD dwBytesSent = 0;
+ rc = WSAIoctl(m_socket, dwCtlCode, &dwCtlCode, sizeof(dwCtlCode), NULL,
+ 0, &dwBytesSent, NULL, NULL);
+ if ( rc == SOCKET_ERROR )
+ {
+ log_socket_error("IGMP WSAIoctl()");
+ return false;
+ }
+#endif
+
+
+ if ( Membership(IGMP_ALL_HOSTS, true)
+ && Membership(IGMP_ALL_V3REPORTS, true)
+ && Membership(IGMP_ALL_ROUTER, true))
+ {
+ Start();
+ return true;
+ }
+
+ return false;
+}
+
+void cIgmpListener::Action()
+{
+ int recvlen;
+ struct pollfd p;
+ char recv_buf[8192];
+
+
+ p.fd = m_socket;
+ p.events = POLLIN;
+
+ while (Running())
+ {
+ switch (poll(&p,1,1000)) {
+ case -1:
+#ifndef WIN32
+ if ( (errno != EINTR) && (errno != EWOULDBLOCK) )
+#else
+ int Rt;
+ Rt = WSAGetLastError();
+ if ( (Rt != WSAEINTR) && (Rt != WSAEWOULDBLOCK) )
+#endif
+ log_socket_error( "IGMP poll()" );
+ case 0:
+ continue;
+
+ default:
+ if (!(p.revents&POLLIN))
+ continue;
+
+ recvlen = recv(m_socket, (char*)&recv_buf,
+ sizeof(recv_buf), 0);
+ if (recvlen < 0)
+ {
+#ifndef WIN32
+ if ( (errno == EINTR) || (errno == EWOULDBLOCK) )
+#else
+ int Rt;
+ Rt = WSAGetLastError();
+ if ( (Rt == WSAEINTR) || (Rt == WSAEWOULDBLOCK) )
+#endif
+ continue;
+ log_socket_error("IGMP recv()");
+ goto out;
+ }
+ Parse(recv_buf, recvlen);
+ break;
+ }
+ }
+out:;
+}
+
+void cIgmpListener::Parse(char* buffer, int len)
+{
+ char* buf = buffer;
+ in_addr_t groupaddr;
+ in_addr_t senderaddr;
+
+ // Make sure we can at least extract the IP version
+ if (len < (int) sizeof(char))
+ {
+ return;
+ }
+ // Check that IP version is IPV4 and that payload is IGMP
+ if ( (HI_BYTE(*buf) == 4) && ( *(buf+9) == IPPROTO_IGMP) )
+ {
+ if (!quiet) {
+ printf("IGMP: SrcAddr: %d.%d.%d.%d -> ",
+ (unsigned char) *(buf+12),
+ (unsigned char) *(buf+13),
+ (unsigned char) *(buf+14),
+ (unsigned char) *(buf+15) );
+ printf("DstAddr: %d.%d.%d.%d\n",
+ (unsigned char) *(buf+16),
+ (unsigned char) *(buf+17),
+ (unsigned char) *(buf+18),
+ (unsigned char) *(buf+19) );
+ }
+ memcpy(&senderaddr, buf+12, 4);
+
+ // skip rest of ip header and move to next to
+ // next protocol header
+ len -= LO_BYTE(*buf) * 4;
+ buf += LO_BYTE(*buf) * 4;
+
+ uint16_t chksum = ((*(buf+3)<<8)&0xFF00 ) | (*(buf+2) & 0x00FF);
+ *(buf+2) = 0;
+ *(buf+3) = 0;
+ if (chksum != inetChecksum((uint16_t *)buf, len))
+ {
+ printf("IGMP: INVALID CHECKSUM 0x%04x 0x%04x - "
+ "discarding packet.\n", chksum,
+ inetChecksum((uint16_t *)buf, len));
+ return;
+ }
+
+ if ( (len == 8) )
+ {
+ if ( *buf == 0x11 )
+ {
+ if (!quiet) {
+ printf("IGMP: Version: %s, Type: "
+ "Membership Query\n",
+ (*(buf+1) == 0 ) ? "1":"2");
+ printf(" Group: %d.%d.%d.%d\n",
+ (unsigned char) *(buf+4),
+ (unsigned char) *(buf+5),
+ (unsigned char) *(buf+6),
+ (unsigned char) *(buf+7));
+ }
+ memcpy(&groupaddr, buf+4, 4);
+ m_IgmpMain->ProcessIgmpQueryMessage( groupaddr,
+ senderaddr );
+ }
+ else if ( *buf == 0x12 )
+ {
+ if (!quiet)
+ printf("IGMP: Version: 1, Type: "
+ "Membership Report\n");
+ }
+ else if (*buf == 0x16)
+ {
+ if (!quiet)
+ printf("IGMP: Version: 2, Type: "
+ "Membership Report\n");
+ }
+ else if (*buf == 0x17)
+ {
+ if (!quiet)
+ printf("IGMP: Version: 2, Type: "
+ "Leave Group\n");
+ }
+
+ if( !quiet)
+ printf(" Group: %d.%d.%d.%d\n",
+ (unsigned char) *(buf+4),
+ (unsigned char) *(buf+5),
+ (unsigned char) *(buf+6),
+ (unsigned char) *(buf+7));
+ memcpy(&groupaddr, buf+4, 4);
+ m_IgmpMain->ProcessIgmpReportMessage( (int) *buf,
+ groupaddr, senderaddr);
+ }
+ else if ( (*buf == 0x11) && (len > 8) )
+ {
+ if (!quiet) {
+ printf("IGMP: Version: 3, Type: "
+ "Membership Query, Maximum "
+ "Response Time: %d\n",*(buf+1));
+ printf(" Group: %d.%d.%d.%d\n",
+ (unsigned char) *(buf+4),
+ (unsigned char) *(buf+5),
+ (unsigned char) *(buf+6),
+ (unsigned char) *(buf+7));
+ }
+ memcpy(&groupaddr, buf+4, 4);
+ m_IgmpMain->ProcessIgmpQueryMessage( groupaddr,
+ senderaddr );
+
+ }
+ else if ( (*buf == 0x22) && (len > 8) )
+ {
+ unsigned short numrecords = ntohs((unsigned short)*
+ (buf+7)<<8|*(buf+6));
+ if (!quiet)
+ printf("IGMP: Version: 3, Type: Membership "
+ "Report, Number of Records: %d\n",
+ numrecords);
+
+ // Skip Header and move to records
+ buf += 8;
+ for(int i = 0; i<numrecords; i++)
+ {
+ unsigned short numsources = (unsigned short)*
+ (buf+3)<<8|*(buf+2);
+ if (!quiet) {
+ printf(" ---> Record No: %d, Type: "
+ "%d, Number of Sources: %d\n",
+ i+1, *buf, numsources);
+ printf(" Group: %d.%d.%d.%d\n",
+ (unsigned char) *(buf+4),
+ (unsigned char) *(buf+5),
+ (unsigned char) *(buf+6),
+ (unsigned char) *(buf+7));
+ }
+ memcpy(&groupaddr, buf+4, 4);
+ m_IgmpMain->ProcessIgmpReportMessage(
+ (int) *buf, groupaddr, senderaddr);
+
+ if (!quiet) for(int j = 0; j<numsources; j++)
+ {
+ printf(" Sources: "
+ "%d.%d.%d.%d\n",
+ (unsigned char) *(buf+j*4),
+ (unsigned char) *(buf+j*4+1),
+ (unsigned char) *(buf+j*4+2),
+ (unsigned char) *(buf+j*4+3));
+ // move to next record: header bytes +
+ // aux data len
+ }
+ buf += 8 + *(buf+1) + (numsources *4);
+ }
+ }
+ else
+ {
+ printf("IGMP: Invalid packet\n");
+ }
+ }
+
+}
+
+void cIgmpListener::IGMPSendQuery(in_addr_t Group, int Timeout)
+{
+ struct sockaddr_in dst;
+ uint16_t query[6];
+
+ dst.sin_family = AF_INET;
+ dst.sin_port = IPPROTO_IGMP;
+ dst.sin_addr.s_addr = Group;
+
+ query[0] = ((Timeout * 10)&0xFF)<<8 | 0x11 ; // Membership Query
+ query[1] = 0x0000; // Checksum
+ if ( Group == IGMP_ALL_HOSTS )
+ { // General Query
+ query[2] = 0x0000;
+ query[3] = 0x0000;
+ }
+ else
+ { // Group Query
+ memcpy(&(query[2]), &Group, 4);
+ }
+ query[4] = IGMP_QUERY_INTERVAL<<8 | 0x00 ; // S=0, QRV=0
+ query[5] = 0x0000; // 0 sources
+ uint16_t chksum = inetChecksum( (uint16_t *) &query, sizeof(query));
+ query[1] = chksum;
+
+ for (int i = 0; i < 5 && ::sendto(m_socket, (const char*) &query,
+ sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) < 0 ; i++)
+ {
+#ifndef WIN32
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+#else
+ if (WSAGetLastError() != WSAEWOULDBLOCK)
+#endif
+ {
+ printf("IGMP: unable to send query for group %s:",
+ inet_ntoa(dst.sin_addr));
+ log_socket_error("IGMP sendto()");
+ break;
+ }
+
+ cCondWait::SleepMs(10);
+ }
+}
+
+
+// -------------------------------------------------------------------------------------------------------------------------
+
+cIgmpMain::cIgmpMain(cStreamer* streamer, iface_t bindif, int table)
+ : cThread("IGMP timeout handler")
+{
+ m_bindaddr = bindif.ipaddr;
+ m_bindif = bindif;
+ m_table = table;
+
+ m_IgmpListener = new cIgmpListener(this);
+ m_StartupQueryCount = IGMP_STARTUP_QUERY_COUNT;
+ m_Querier = true;
+ m_streamer = streamer;
+ m_stopping = 0;
+ TV_CLR(m_GeneralQueryTimer);
+}
+
+cIgmpMain::~cIgmpMain(void)
+{
+}
+
+void cIgmpMain::Destruct(void)
+{
+ if (m_IgmpListener)
+ {
+ m_IgmpListener->Destruct();
+ }
+
+ // Wake up timeout thread in case it is currently sleeping
+ m_stopping=1;
+ m_CondWait.Signal();
+
+ // Now try to stop thread. After 3 seconds it will be canceled.
+ Cancel(3);
+
+ cMulticastGroup *del = NULL;
+
+ for (cMulticastGroup *group = m_Groups.First(); group;
+ group = m_Groups.Next(group))
+ {
+ if ( group->stream )
+ {
+ m_streamer->StopMulticast(group);
+ }
+ if (del)
+ m_Groups.Del(del);
+ del = group;
+ }
+ if (del)
+ m_Groups.Del(del);
+
+}
+
+bool cIgmpMain::StartListener(void)
+{
+ if ( m_IgmpListener && m_IgmpListener->Initialize(m_bindif, m_table) )
+ {
+ Start();
+ return true;
+ }
+ else
+ {
+ printf("cIgmpMain: Cannot create IGMP listener.\n");
+ return false;
+ }
+}
+
+void cIgmpMain::ProcessIgmpQueryMessage(in_addr_t groupaddr,
+ in_addr_t senderaddr)
+{
+ if (ntohl(senderaddr) < ntohl(m_bindaddr))
+ {
+ in_addr sender;
+ sender.s_addr = senderaddr;
+ printf("IGMP: Another Querier with lower IP address (%s) "
+ "is active.\n", inet_ntoa(sender));
+ IGMPStartOtherQuerierPresentTimer();
+ }
+}
+
+void cIgmpMain::ProcessIgmpReportMessage(int type, in_addr_t groupaddr,
+ in_addr_t senderaddr)
+{
+ cMulticastGroup* group;
+
+ if ( !m_streamer->IsGroupinRange(groupaddr) )
+ return;
+
+ LOCK_THREAD;
+
+ switch (type)
+ {
+ case IGMPV3_MR_MODE_IS_INCLUDE:
+ break;
+
+ case IGMPV1_MEMBERSHIP_REPORT:
+ case IGMPV2_MEMBERSHIP_REPORT:
+ case IGMPV3_MR_MODE_IS_EXCLUDE:
+ case IGMPV3_MR_CHANGE_TO_EXCLUDE:
+ group = FindGroup(groupaddr);
+ if (!group)
+ {
+ group = new cMulticastGroup(groupaddr);
+ m_Groups.Add(group);
+ }
+ if (!group->stream)
+ {
+ m_streamer->StartMulticast(group);
+ }
+ IGMPStartTimer(group, senderaddr);
+ if (type == IGMPV1_MEMBERSHIP_REPORT)
+ IGMPStartV1HostTimer(group);
+ break;
+
+ case IGMPV2_LEAVE_GROUP:
+ case IGMPV3_MR_CHANGE_TO_INCLUDE:
+ group = FindGroup(groupaddr);
+ if (group && !TV_SET(group->v1timer))
+ {
+ if (group->reporter == senderaddr)
+ {
+ IGMPStartTimerAfterLeave(group, m_Querier ?
+ IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : 1);
+ //Igmp->igmp_code);
+ if (m_Querier)
+ IGMPSendGroupQuery(group);
+ IGMPStartRetransmitTimer(group);
+ }
+ m_CondWait.Signal();
+ }
+ break;
+
+ case IGMPV3_MR_ALLOW_NEW_SOURCES:
+ printf("IGMPv3 Group Record Type \"ALLOW_NEW_SOURCES\" "
+ "ignored.\n");
+ break;
+ case IGMPV3_MR_BLOCK_OLD_SOURCES:
+ printf("IGMPv3 Group Record Type \"BLOCK_OLD_SOURCES\" "
+ "ignored.\n");
+ break;
+ default:
+ printf("Unknown IGMPv3 Group Record Type.\n");
+ }
+}
+
+void cIgmpMain::Action()
+{
+ while (Running())
+ {
+ struct timeval now;
+ struct timeval next;
+
+ gettimeofday(&now, NULL);
+ TV_CPY(next, now);
+ next.tv_sec += IGMP_QUERY_INTERVAL;
+
+ cMulticastGroup *del = NULL;
+
+ {// <-- ThreadLock is locked only within this block!
+ LOCK_THREAD;
+
+ if (TV_CMP(m_GeneralQueryTimer, <, now)) {
+ if (!quiet)
+ printf("IGMP: Starting General Query.\n");
+ IGMPSendGeneralQuery();
+ IGMPStartGeneralQueryTimer();
+ }
+ if (TV_CMP(next, >, m_GeneralQueryTimer))
+ TV_CPY(next, m_GeneralQueryTimer);
+
+ for (cMulticastGroup *group = m_Groups.First(); group;
+ group = m_Groups.Next(group))
+ {
+ if (TV_CMP(group->timeout, <, now))
+ {
+ m_streamer->StopMulticast(group);
+ IGMPClearRetransmitTimer(group);
+ if (del)
+ m_Groups.Del(del);
+ del = group;
+ }
+ else if (m_Querier && TV_SET(group->retransmit)
+ && TV_CMP(group->retransmit, <, now)) {
+ IGMPSendGroupQuery(group);
+ IGMPStartRetransmitTimer(group);
+ if (TV_CMP(next, >,
+ group->retransmit))
+ TV_CPY(next,
+ group->retransmit);
+ }
+ else if (TV_SET(group->v1timer) &&
+ TV_CMP(group->v1timer, <, now)) {
+ TV_CLR(group->v1timer);
+ }
+ else {
+ if (TV_CMP(next, >, group->timeout))
+ TV_CPY(next, group->timeout);
+ if (TV_SET(group->retransmit) &&
+ TV_CMP(next, >,
+ group->retransmit))
+ TV_CPY(next, group->retransmit);
+ if (TV_SET(group->v1timer) &&
+ TV_CMP(next, >, group->v1timer))
+ TV_CPY(next, group->v1timer);
+ }
+ }
+ if (del)
+ m_Groups.Del(del);
+ }
+
+ int sleep = (next.tv_sec - now.tv_sec) * 1000;
+ sleep += (next.tv_usec - now.tv_usec) / 1000;
+ if (next.tv_usec < now.tv_usec)
+ sleep += 1000;
+ if (m_stopping)
+ sleep = 100;
+ else if (!sleep)
+ continue;
+ if (!quiet)
+ printf("IGMP main thread: Sleeping %d ms\n", sleep);
+ m_CondWait.Wait(sleep);
+ }
+}
+
+cMulticastGroup* cIgmpMain::FindGroup(in_addr_t Group) const
+{
+ cMulticastGroup *group = m_Groups.First();
+ while (group && group->group != Group)
+ group = m_Groups.Next(group);
+ return group;
+}
+
+void cIgmpMain::IGMPStartGeneralQueryTimer()
+{
+ m_Querier = true;
+ if (m_StartupQueryCount) {
+ gettimeofday(&m_GeneralQueryTimer, NULL);
+ m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL;
+ m_StartupQueryCount--;
+ }
+ else {
+ gettimeofday(&m_GeneralQueryTimer, NULL);
+ m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL;
+ }
+}
+
+void cIgmpMain::IGMPStartOtherQuerierPresentTimer()
+{
+ m_Querier = false;
+ m_StartupQueryCount = 0;
+ gettimeofday(&m_GeneralQueryTimer, NULL);
+ m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
+}
+
+void cIgmpMain::IGMPSendGeneralQuery()
+{
+ m_IgmpListener->IGMPSendQuery(IGMP_ALL_HOSTS,
+ IGMP_QUERY_RESPONSE_INTERVAL);
+}
+
+void cIgmpMain::IGMPStartRetransmitTimer(cMulticastGroup* Group)
+{
+ gettimeofday(&Group->retransmit, NULL);
+ TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
+}
+
+void cIgmpMain::IGMPClearRetransmitTimer(cMulticastGroup* Group)
+{
+ TV_CLR(Group->retransmit);
+}
+
+// Group state actions
+void cIgmpMain::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member)
+{
+ gettimeofday(&Group->timeout, NULL);
+ Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
+ TV_CLR(Group->retransmit);
+ Group->reporter = Member;
+
+}
+
+void cIgmpMain::IGMPStartV1HostTimer(cMulticastGroup* Group)
+{
+ gettimeofday(&Group->v1timer, NULL);
+ Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
+}
+
+void cIgmpMain::IGMPStartTimerAfterLeave(cMulticastGroup* Group,
+ unsigned int MaxResponseTimeTs)
+{
+ //Group->Update(time(NULL) + MaxResponseTime
+ //* IGMP_LAST_MEMBER_QUERY_COUNT / 10);
+ MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT;
+ gettimeofday(&Group->timeout, NULL);
+ TV_ADD(Group->timeout, MaxResponseTimeTs);
+ TV_CLR(Group->retransmit);
+ Group->reporter = 0;
+}
+
+void cIgmpMain::IGMPSendGroupQuery(cMulticastGroup* Group)
+{
+ m_IgmpListener->IGMPSendQuery(Group->group,
+ IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
+}
+