#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
#include <event.h>
#include "igmp.h"
#include "dvmrpd.h"
#include "dvmrp.h"
#include "log.h"
#include "dvmrpe.h"
int igmp_chksum(struct igmp_hdr *);
int
send_igmp_query(struct iface *iface, struct group *group)
{
struct igmp_hdr igmp_hdr;
struct sockaddr_in dst;
struct ibuf *buf;
int ret = 0;
log_debug("send_igmp_query: interface %s", iface->name);
if (iface->passive)
return (0);
if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL)
fatal("send_igmp_query");
memset(&igmp_hdr, 0, sizeof(igmp_hdr));
igmp_hdr.type = PKT_TYPE_MEMBER_QUERY;
if (group == NULL) {
igmp_hdr.grp_addr = 0;
switch (iface->igmp_version) {
case 1:
break;
case 2:
igmp_hdr.max_resp_time = iface->query_resp_interval;
break;
default:
fatal("send_igmp_query: invalid igmp version");
}
} else {
igmp_hdr.grp_addr = group->addr.s_addr;
igmp_hdr.max_resp_time = iface->last_member_query_interval;
}
ibuf_add(buf, &igmp_hdr, sizeof(igmp_hdr));
dst.sin_family = AF_INET;
dst.sin_len = sizeof(struct sockaddr_in);
inet_pton(AF_INET, AllSystems, &dst.sin_addr);
ret = send_packet(iface, buf, &dst);
ibuf_free(buf);
return (ret);
}
void
recv_igmp_query(struct iface *iface, struct in_addr src, char *buf,
u_int16_t len)
{
struct igmp_hdr igmp_hdr;
struct group *group;
log_debug("recv_igmp_query: interface %s", iface->name);
if (len < sizeof(igmp_hdr)) {
log_debug("recv_igmp_query: invalid IGMP report, interface %s",
iface->name);
return;
}
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
iface->recv_query_resp_interval = igmp_hdr.max_resp_time;
if (igmp_chksum(&igmp_hdr) == -1) {
log_debug("recv_igmp_query: invalid chksum, interface %s",
iface->name);
return;
}
if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) {
if_fsm(iface, IF_EVT_QRECVD);
iface->querier = src;
return;
}
if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) {
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
log_debug("recv_igmp_query: invalid group, "
"interface %s", iface->name);
return;
}
if ((group = group_list_add(iface, igmp_hdr.grp_addr))
!= NULL)
group_fsm(group, GRP_EVT_QUERY_RCVD);
}
}
void
recv_igmp_report(struct iface *iface, struct in_addr src, char *buf,
u_int16_t len, u_int8_t type)
{
struct igmp_hdr igmp_hdr;
struct group *group;
log_debug("recv_igmp_report: interface %s", iface->name);
if (len < sizeof(igmp_hdr)) {
log_debug("recv_igmp_report: invalid IGMP report, interface %s",
iface->name);
return;
}
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
if (igmp_chksum(&igmp_hdr) == -1) {
log_debug("recv_igmp_report: invalid chksum, interface %s",
iface->name);
return;
}
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
log_debug("recv_igmp_report: invalid group, interface %s",
iface->name);
return;
}
if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL)
return;
if (iface->state == IF_STA_QUERIER) {
switch (type) {
case PKT_TYPE_MEMBER_REPORTv1:
group_fsm(group, GRP_EVT_V1_REPORT_RCVD);
break;
case PKT_TYPE_MEMBER_REPORTv2:
group_fsm(group, GRP_EVT_V2_REPORT_RCVD);
break;
default:
fatalx("recv_igmp_report: unknown IGMP report type");
}
} else {
group_fsm(group, GRP_EVT_REPORT_RCVD);
}
}
void
recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf,
u_int16_t len)
{
struct igmp_hdr igmp_hdr;
struct group *group;
log_debug("recv_igmp_leave: interface %s", iface->name);
if (iface->state != IF_STA_QUERIER)
return;
if (len < sizeof(igmp_hdr)) {
log_debug("recv_igmp_leave: invalid IGMP leave, interface %s",
iface->name);
return;
}
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr));
if (igmp_chksum(&igmp_hdr) == -1) {
log_debug("recv_igmp_leave: invalid chksum, interface %s",
iface->name);
return;
}
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) {
log_debug("recv_igmp_leave: invalid group, interface %s",
iface->name);
return;
}
if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) {
group_fsm(group, GRP_EVT_LEAVE_RCVD);
}
}
int
igmp_chksum(struct igmp_hdr *igmp_hdr)
{
u_int16_t chksum;
chksum = igmp_hdr->chksum;
igmp_hdr->chksum = 0;
if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr)))
return (-1);
return (0);
}