/* $Id: igmp.c,v 1.1 1999/08/23 16:18:32 naamato Exp $ */
/*
 * Copyright(c) 1998 by Hitachi.Ltd All rights reserved.
 *
 */

#include "defs.h"
#include <net/route.h>
#include <netinet/ip_mroute.h>
#include "igmp_def.h"
#include "interface.h"

#define BUF_SIZE  1024

#if 0
#if !(defined(BSD) && (BSD >= 199103))
struct ip_opts {
    struct in_addr ip_dst;
    char ip_opts[40];
};

#ifndef IPOPT_RA
#define IPOPT_RA   148
#endif
#endif
#endif

#if (defined(_BSDI_VERSION) || defined(SOLARIS))
/*
 * Struct used to communicate from kernel to multicast router
 * note the convenient similarity to an IP packet
 */
struct igmpmsg {
    u_long          unused1;
    u_long          unused2;
    u_char          im_msgtype;                 /* what type of message     */
#define IGMPMSG_NOCACHE         1
#define IGMPMSG_WRONGVIF        2
    u_char          im_mbz;                     /* must be zero             */
    u_char          im_vif;                     /* vif rec'd on             */
    u_char          unused3;
    struct in_addr  im_src, im_dst;
};
#endif

void igmp_trace();
int igmp_sock;

char *inet_atos();
char *send_buf;
char *recv_buf;

extern struct if_info *if_list;
extern int trace_mode;

struct _igmp_recv_func {
    void (*recv_routine)();
    int dummry;
};

static struct _igmp_recv_func igmp_recv_func[IGMP_MAX_TYPE];
struct _igmp_recv_func igmp_sg_func;

FILE *trace_fp=NULL;
long router_alert;
#if 0
struct ip_opts ra; /* router alert option */
#endif

/*
 *
 */
void igmp_init()
{
    int bool;
    char *rap;

    if((igmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP)) < 0) {
        perror("IGMP socket");
	exit(1);
    }
    send_buf = malloc(BUF_SIZE);
    recv_buf = malloc(BUF_SIZE);
    bzero(send_buf,BUF_SIZE);
    bzero(recv_buf,BUF_SIZE);

    rap = (char *)&router_alert;
#if 0
    rap[0] = ra.ip_opts[0] = IPOPT_RA;
    rap[1] = ra.ip_opts[1] = RA_OPT_LEN;
    rap[2] = ra.ip_opts[2] = 0x00;
    rap[3] = ra.ip_opts[3] = 0x00;
#endif
#ifdef IPOPT_RA
    rap[0] = IPOPT_RA;
    rap[1] = RA_OPT_LEN;
    rap[2] = 0x00;
    rap[3] = 0x00;
#endif    
}

/*
 *
 */
void multicast_init()
{
    int v;
    struct vifctl vc;
    struct if_info *ifp;
    int i;

    v=1;
    if(setsockopt(igmp_sock,IPPROTO_IP, MRT_INIT,(char *)&v, sizeof(int))<0)
         perror("setsockopt MRT_INIT");

    for(ifp=if_list,i=0;ifp;ifp=ifp->next,i++) {

	vc.vifc_vifi  = i;
	vc.vifc_flags = 0;
	vc.vifc_threshold = 1;
	vc.vifc_rate_limit = 0;
	vc.vifc_lcl_addr.s_addr = htonl(IF_ADDR(ifp->ipaddr));

	if(setsockopt(igmp_sock, IPPROTO_IP, MRT_ADD_VIF,
		  (char *)&vc, sizeof(vc)) < 0)
	    perror("setsockopt MRT_ADD_VIF");
    }
}

/*
 *
 */
void init_assert()
{
    int v;

    v=1;
    if(setsockopt(igmp_sock,IPPROTO_IP, MRT_ASSERT,(char *)&v, sizeof(int))<0)
         perror("setsockopt MRT_ASSERT");
}

/*
 *
 */
void multicast_tunnel_vif(vif,lcl_addr,rmt_addr)
int vif;
u_long lcl_addr;
u_long rmt_addr;
{
    int v;
    struct vifctl vc;
    int i;

    vc.vifc_vifi  = vif;
    vc.vifc_flags = VIFF_TUNNEL;
    vc.vifc_threshold = 1;
    vc.vifc_rate_limit = 0;
    vc.vifc_lcl_addr.s_addr = htonl(lcl_addr);
    vc.vifc_rmt_addr.s_addr = htonl(rmt_addr);

    if(setsockopt(igmp_sock, IPPROTO_IP, MRT_ADD_VIF,
		  (char *)&vc, sizeof(vc)) < 0) 
	perror("setsockopt MRT_ADD_VIF");
}

/*
 *
 */
void multicast_term()
{
    int v;
    struct vifctl vc;
    struct if_info *ifp;
    int i;

    for(ifp=if_list,i=0;ifp;ifp=ifp->next,i++) {
	vc.vifc_vifi  = i;
	vc.vifc_flags = 0;
	vc.vifc_threshold = 1;
	vc.vifc_rate_limit = 0;
	vc.vifc_lcl_addr.s_addr = htonl(IF_ADDR(ifp->ipaddr));
	if(setsockopt(igmp_sock, IPPROTO_IP, MRT_DEL_VIF,
		  (char *)&vc, sizeof(vc)) < 0)
	    perror("setsockopt MRT_DEL_VIF");
    }

    if(setsockopt(igmp_sock,IPPROTO_IP, MRT_DONE,(char *)NULL, 0)<0)
         perror("setsockopt MRT_DONE");

}

/*
 *
 */
void kernel_add_entry(src,grp,iif)
u_long src,grp;
int iif;
{
    struct if_info *ifp;
    struct mfcctl mc;
    int i;
    
    printf(" kernel add entry (%s,%s,%d)\n",
	   inet_ipstr(src,ipstr1),inet_ipstr(grp,ipstr2),iif);
    mc.mfcc_origin.s_addr = src;
    mc.mfcc_mcastgrp.s_addr = grp;
    mc.mfcc_parent = iif;

    /* All oif=NULL */
    for(ifp=if_list,i=0;ifp;ifp=ifp->next,i++) {
	mc.mfcc_ttls[i] = 0;
    }

    if(setsockopt(igmp_sock,IPPROTO_IP,MRT_ADD_MFC,
		  (char *)&mc,sizeof(mc)) < 0) {
	perror("setsockopt MRT_ADD_MFC");
    }
}

/*
 *
 */
void kernel_del_entry(src,grp)
u_long src,grp;
{
    struct mfcctl mc;
    int i;
    
    mc.mfcc_origin.s_addr = src;
    mc.mfcc_mcastgrp.s_addr = grp;

    if(setsockopt(igmp_sock,IPPROTO_IP,MRT_DEL_MFC,
		  (char *)&mc,sizeof(mc)) < 0) {
	perror("setsockopt MRT_DEL_MFC");
    }
}

/*
 *
 */
void mfc_add_entry(src,grp,iif,oifs)
u_long src,grp;
int iif;
long oifs;
{
    struct mfcctl mc;
    int i;
    long oif;

    mc.mfcc_origin.s_addr = src;
    mc.mfcc_mcastgrp.s_addr = grp;
    mc.mfcc_parent = iif;

    for(i=0,oif=oifs;i<32;i++,oif=oifs>>i) {
	if(0x00000001 & oif) {
	    mc.mfcc_ttls[i] = 1;
	}
	else {
	    mc.mfcc_ttls[i] = 0;
	}
    }

#if 1
    if(setsockopt(igmp_sock,IPPROTO_IP,MRT_ADD_MFC,
		  (char *)&mc,sizeof(mc)) < 0) {
	perror("setsockopt MRT_ADD_MFC");
    }
#endif
}

/*
 *
 */
void mfc_del_entry(src,grp)
u_long src,grp;
{
    struct mfcctl mc;

    mc.mfcc_origin.s_addr = src;
    mc.mfcc_mcastgrp.s_addr = grp;

#if 1
    if(setsockopt(igmp_sock,IPPROTO_IP,MRT_DEL_MFC,
		  (char *)&mc,sizeof(mc)) < 0) {
	perror("setsockopt MRT_DEL_MFC");
    }
#endif
}

/*
 *
 */
void igmp_recv_register(type, callback)
int type;
void callback();
{
    igmp_recv_func[type-IGMP_BASE_TYPE].recv_routine = callback;
}

/*
 *
 */
void sg_recv_register(callback)
void callback();
{
    igmp_sg_func.recv_routine = callback;
}

/*
 *
 */
void igmp_query_send(if_name,src,dst,grp,time,isalert)
char *if_name;
u_long src,dst;
u_long grp;
u_int time;
int isalert;
{
    struct ip *ip;
    struct igmp *igmp;
    int rtn;
    u_long ifaddr;
    int iplen;

    if(if_name) {
        ip = (struct ip *)send_buf;
 	if(isalert) {
#ifdef IPOPT_RA
	    igmp = (struct igmp *)((char *)(ip+1)+RA_OPT_LEN);
	    iplen = sizeof(struct ip) + RA_OPT_LEN + sizeof(struct igmp);
#else
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
#endif	    
	}
	else {
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
	}
    }
    else {
        igmp = (struct igmp *)send_buf;
    }

    igmp->igmp_type = IGMP_MEMBERSHIP_QUERY;
    igmp->igmp_code = (u_char)time;
    igmp->igmp_group = htonl(grp);
    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = in_cksum((u_short *)igmp, sizeof(struct igmp));

    if(!dst) { 
        if(grp) dst = grp;
	else dst = ALL_HOSTS_GROUP;
    } 

    if(if_name) {
        ifaddr = get_ifaddr_byname(if_name);
	if(!src) src = ifaddr;
	rtn = igmp_send(htonl(ifaddr),htonl(src),ip,htonl(dst),iplen,IGMP_TTL,isalert);
    }
    else {
        igmp_send2(igmp,htonl(dst),sizeof(struct igmp),IGMP_TTL,isalert);
    }

}

/*
 *
 */
void igmp_report_send(if_name,src,dst,grp,ver,isalert)
char *if_name;
u_long src,dst,grp;
int ver;
int isalert;
{
    struct ip *ip;
    struct igmp *igmp;
    int rtn;
    u_long ifaddr;
    int iplen;

    if(if_name) {
        ip = (struct ip *)send_buf;
 	if(isalert) {
#ifdef IPOPT_RA
	    igmp = (struct igmp *)((char *)(ip+1)+RA_OPT_LEN);
	    iplen = sizeof(struct ip) + RA_OPT_LEN + sizeof(struct igmp);
#else
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
#endif	    
	}
	else {
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
	}
    }
    else {
        igmp = (struct igmp *)send_buf;
    }

    if(ver == IGMP_VERSION_1) igmp->igmp_type = IGMP_MEMBERSHIP_REPORT;
    else if(ver == IGMP_VERSION_2) igmp->igmp_type = IGMP_V2_MEMBERSHIP_REPORT;
    igmp->igmp_code = 0;
    igmp->igmp_group = htonl(grp);
    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = in_cksum((u_short *)igmp, sizeof(struct igmp));

    if(!dst) { 
        dst = grp;
    }

    if(if_name) {
        ifaddr = get_ifaddr_byname(if_name);
	if(!src) src = ifaddr;
	rtn = igmp_send(htonl(ifaddr),htonl(src),ip,htonl(dst),iplen,IGMP_TTL,isalert);
    }
    else {
        igmp_send2(igmp,htonl(dst),sizeof(struct igmp),IGMP_TTL,isalert);
    }

}

/*
 *
 */
void igmp_leave_send(if_name,src,dst,grp,isalert)
char *if_name;
u_long src,dst,grp;
int isalert;
{
    struct ip *ip;
    struct igmp *igmp;
    int rtn;
    u_long ifaddr;
    int iplen;

    if(if_name) {
        ip = (struct ip *)send_buf;
	if(isalert) {
#ifdef IPOPT_RA
	    igmp = (struct igmp *)((char *)(ip+1)+RA_OPT_LEN);
	    iplen = sizeof(struct ip) + RA_OPT_LEN + sizeof(struct igmp);
#else
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
#endif	    
	}
	else {
	    igmp = (struct igmp *)(ip+1);
	    iplen = sizeof(struct ip) + sizeof(struct igmp);
	}
    }
    else {
        igmp = (struct igmp *)send_buf;
    }

    igmp->igmp_type = IGMP_MEMBERSHIP_LEAVE;
    igmp->igmp_code = 0;
    igmp->igmp_group = htonl(grp);
    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = in_cksum((u_short *)igmp, sizeof(struct igmp));

    if(!dst) { 
        dst = ALL_ROUTERS_GROUP;
    } 

    if(if_name) {
        ifaddr = get_ifaddr_byname(if_name);
	if(!src) src = ifaddr;
	rtn = igmp_send(htonl(ifaddr),htonl(src),ip,htonl(dst),iplen,IGMP_TTL,isalert);
    }
    else {
        igmp_send2(igmp,htonl(dst),sizeof(struct igmp),IGMP_TTL,isalert);
    }

}

/*
 *
 */
int igmp_send(ifaddr,src,ip,dst,iplen,ttl,isalert)
u_long ifaddr,src;
struct ip *ip;
u_long dst;
int iplen;
int ttl;
int isalert;
{
    u_char loop;
    int rtn;
    struct sockaddr_in sdst;
    u_char ip_ttl;
    struct in_addr adr;
    int bool ;

    bool = 1;
    if (setsockopt(igmp_sock, IPPROTO_IP, IP_HDRINCL,(char *)&bool, sizeof(bool)) < 0)
        perror("setsockopt IP_HDRINCL");

    ip->ip_v = IPVERSION;
#ifdef IPOPT_RA
    if(isalert) {
	ip->ip_hl = (sizeof(struct ip) + RA_OPT_LEN) >> 2;
	*(u_long *)(ip + 1) = router_alert;
    }
    else {
	ip->ip_hl = sizeof(struct ip) >> 2;
    }
#else
    ip->ip_hl = sizeof(struct ip) >> 2;
#endif
    ip->ip_tos = 0;
    ip->ip_off = 0;
    ip->ip_p = IPPROTO_IGMP;
    ip->ip_ttl = ip_ttl = ttl;
    ip->ip_src.s_addr = src;
    ip->ip_dst.s_addr = dst;
    ip->ip_len = iplen;

    if(IN_MULTICAST(ntohl(dst))) {
       if (setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_TTL, (char *)&ip_ttl,sizeof(ip_ttl)) < 0) 
          perror("setsockopt IP_MULTICAST_TTL");

       adr.s_addr = ifaddr;
       if(setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_IF,
		     (char *)&adr, sizeof(adr)) < 0)
	  perror("setsockopt IP_MULTICAST_IF");

       loop=1;
       if(setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_LOOP,(char *)&loop,sizeof(loop)) < 0)
	  perror("setsockopt IP_MULTICAST_LOOP");
    }

    bzero(&sdst,sizeof(sdst));
    sdst.sin_family = AF_INET;
    sdst.sin_addr.s_addr = dst;
    if(sendto(igmp_sock, (char *)ip, ip->ip_len, 0, (struct sockaddr *)&sdst,sizeof(sdst)) < 0) {
        perror("sendto");
    }

    loop=0;
    if(setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_LOOP,(char *)&loop,sizeof(loop)) < 0)
	  perror("setsockopt IP_MULTICAST_LOOP");

    bool = 0;
    if (setsockopt(igmp_sock, IPPROTO_IP, IP_HDRINCL,(char *)&bool, sizeof(bool)) < 0)
        perror("setsockopt IP_HDRINCL");

    return rtn;
}

/*
 *
 */
int igmp_send2(igmp,dst,len,ttl,isalert)
struct igmp *igmp;
u_long dst;
int len;
int ttl;
int isalert;
{
    u_char loop;
    int rtn;
    struct sockaddr_in sdst;
    u_char ip_ttl=ttl;
    int ttl2;
    struct in_addr adr;
    int bool;
#if 0
    struct ip_opts alertopt;  /* rotuer alert */
#endif

    bool = 0;

    if(IN_MULTICAST(ntohl(dst))) {
       if (setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_TTL, (char *)&ip_ttl,sizeof(ip_ttl)) < 0) 
          perror("setsockopt IP_MULTICAST_TTL");

       loop = 1;
       if(setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_LOOP,(char *)&loop,sizeof(loop)) < 0)
	  perror("setsockopt IP_MULTICAST_LOOP");
    }
    else {  /* unicast */
	ttl2 = 1;
#ifdef IP_TTL
	if (setsockopt(igmp_sock, IPPROTO_IP, IP_TTL, (char *)&ttl2, sizeof(int)) < 0)
	    perror("setsockopt IP_TTL");
#endif
    }

/*    bzero(&alertopt.ip_dst,sizeof(alertopt.ip_dst));*/

#ifdef IPOPT_RA
    if(isalert) {
/*	*(u_long *)(alertopt.ip_opts) = htonl(router_alert);*/
	if(setsockopt(igmp_sock, IPPROTO_IP, IP_OPTIONS, (char *)&router_alert,sizeof(router_alert)) < 0) {
	    perror("setsockopt IP_OPTIONS");
	}
    }
#endif

    bzero(&sdst,sizeof(sdst));
    sdst.sin_family = AF_INET;
    sdst.sin_addr.s_addr = dst;
    if(sendto(igmp_sock, (char *)igmp, len, 0, (struct sockaddr *)&sdst,sizeof(sdst)) < 0) {
        perror("sendto");
    }

    loop=0;
    if(setsockopt(igmp_sock,IPPROTO_IP,IP_MULTICAST_LOOP,(char *)&loop,sizeof(loop)) < 0)
	  perror("setsockopt IP_MULTICAST_LOOP");

    return rtn;
}

/*
 *
 */
void igmp_trace(fp,src,dst,igmp)
FILE *fp;
u_long src;
u_long dst;
struct igmp *igmp;
{
    struct in_addr addr;
    char *inet_atos();
    char *igmp_types[]=
	{NULL,"Query","Report",NULL,NULL,NULL,"V2 Report","Leave"};

    if(!trace_mode)
	return;

    ttrace(fp," %s > %s \t %s ",inet_ipstr(src,ipstr1),inet_ipstr(dst,ipstr2),igmp_types[igmp->igmp_type-IGMP_BASE_TYPE]);
    
#if 0
    switch(igmp->igmp_type) {
    case IGMP_MEMBERSHIP_QUERY: fprintf(fp," Query ");
	break;
    case IGMP_MEMBERSHIP_REPORT : fprintf(fp," Report ");
	break;
    case IGMP_V2_MEMBERSHIP_REPORT : fprintf(fp," V2 Report ");
	break;
    case IGMP_MEMBERSHIP_LEAVE: fprintf(fp," Leave ");
	break;
    default : break;
    }
#endif

    if(!fp && trace_mode == TRACE_MODE_PKT) {
	trace2(fp,"\n");
	return;
    };

    trace2(fp," Max Resp. Time: %d ",igmp->igmp_code);
    addr.s_addr = igmp->igmp_group;
    trace2(fp," Group: %s \n",inet_atos(igmp->igmp_group));

}

/*
 *
 */    
void igmp_recv(recvlen)
int recvlen;
{
    u_long  src,dst,grp;
    struct ip *ip;
    struct igmp *igmp;
    struct igmpmsg *igmpctl;
    int data_len, hdr_len, igmp_len;

    ip = (struct ip *)recv_buf;
    src = ip->ip_src.s_addr;
    dst = ip->ip_dst.s_addr;

    if(ip->ip_p == 0) {
	if(src == 0 || dst == 0) 
	    printf(" Illegal Kernel Request Cache \n");
	else {
	    igmpctl = (struct igmpmsg *)(recv_buf + hdr_len);
	    switch(igmpctl->im_msgtype) {
	    case IGMPMSG_NOCACHE:
		if(igmp_sg_func.recv_routine)
		    igmp_sg_func.recv_routine(ntohl(src),ntohl(dst));
		break;
	    case IGMPMSG_WRONGVIF:
		printf(" Packet receiving from oif\n");
		break;
	    default:
		printf("Uknown upcall type \n");
		break;
	    }
	}
	return;
    }

    hdr_len = ip->ip_hl << 2;
    data_len = ip->ip_len;
    if(hdr_len + data_len != recvlen) {
	return;
    }

    igmp = (struct igmp *)(recv_buf + hdr_len);
    grp = igmp->igmp_group;
    igmp_len = data_len - sizeof(struct igmp);
    if(igmp_len < 0) {
	return;
    }

    switch(igmp->igmp_type) {
    case IGMP_MEMBERSHIP_QUERY: if(igmp_recv_func[igmp->igmp_type-IGMP_BASE_TYPE].recv_routine)
	igmp_recv_func[igmp->igmp_type-IGMP_BASE_TYPE].recv_routine(ntohl(src),ntohl(dst),igmp);
	break;
    case IGMP_MEMBERSHIP_REPORT : 
	break;
    case IGMP_PROTO_DVMRP : if(igmp_recv_func[igmp->igmp_type-IGMP_BASE_TYPE].recv_routine)
	igmp_recv_func[igmp->igmp_type-IGMP_BASE_TYPE].recv_routine(ntohl(src),ntohl(dst),igmp,data_len);
	break;
    case IGMP_V2_MEMBERSHIP_REPORT : 
	break;
    case IGMP_MEMBERSHIP_LEAVE: 
	break;
    default : break;
    }

}
