IGMPProxy (二)

1、IGMPProxy流程

main函数

第一步:解析命令行参数

while ((c = getopt(ArgCn, ArgVc, "vdnh")) != -1){
    switch (c) {
    case 'n':
        NotAsDaemon = true; //表示程序将以非守护进程模式运行。
        break;
    case 'd':
        Log2Stderr = true; //表示程序会输出所有消息到标准错误流(stderr)
        NotAsDaemon = true; //表示程序将以非守护进程模式运行。
        break;
    case 'v': //则会增加详细程度(verbosity)
        if (LogLevel == LOG_INFO)
            LogLevel = LOG_DEBUG;
        else
            LogLevel = LOG_INFO;
        break;
    case 'h':
        fputs(Usage, stderr); //输出帮助信息 (Usage) 到标准错误流(stderr),然后程序退出。
        exit(0);
        break;
    default:
        exit(1);
        break;
    }
}
  • ArgCn:传递给 main 函数的参数个数,即 argc 参数。
  • ArgVc:传递给 main 函数的参数数组,即 argv 参数。
  • "vdnh":字符串,指定了程序可以接受的选项字符。每个字符代表一个选项:
    • v: "verbose"(详细模式),用于增加程序的输出详细程度。
    • d: "debug"(调试模式),用于开启调试信息输出。
    • n: "nodaemon"(非守护进程模式),用于防止程序以守护进程方式运行。
    • h:"help"(帮助),用于显示程序的使用帮助信息。

第二步:检查配置文件参数:确保用户在命令行中指定了配置文件路径(configFilePath )。并检查检查root权限:程序需要以root用户权限运行

if (optind != ArgCn - 1) {  //检查是否指定了配置文件作为参数. optind 是getopt函数处理后的下一个待解析参数索引,ArgCn - 1 是最后一个参数的索引。
    fputs("You must specify the configuration file.\n", stderr);
    exit(1);
}
char *configFilePath = ArgVc[optind]; //如果配置文件参数存在,将其存储在 configFilePath 变量中。
if (geteuid() != 0) { //这行代码检查程序是否以 root 用户权限运行
   fprintf(stderr, "igmpproxy: must be root\n");
   exit(1);
}

第三步:加载配置文件:调用loadConfig函数来加载配置文件

if( ! loadConfig( configFilePath ) ) {  //加载配置文件,如果加载失败,会输出错误消息。
    my_log(LOG_ERR, 0, "Unable to load config file...");
    break;
}

        loadConfig函数:initCommonConfig(); 先初始化一些公共常量的配置,INTERVAL_QUERY查询周期125s,INTERVAL_QUERY_RESPONSE查询响应周期10s; openConfigFile(configFile)读取配置文件;token = nextConfigToken(); 获取配置文件的信息,将配置信息保存到全局变量vifconf中。

int loadConfig(char *configFile) {
   
    initCommonConfig();  //用于初始化一些公共常量的配置

    if(!openConfigFile(configFile)) { //尝试打开配置文件,如果打开失败,输出错误消息。
        my_log(LOG_ERR, 0, "Unable to open configfile from %s", configFile);
    }

    // 再从iBuffer中解析出每一个token(是从igmpproxy.conf中解析出一个一个token)
    token = nextConfigToken(); //获取配置文件的下一个令牌(token)
    
    while ( token != NULL ) {
        // 调用 parsePhyintToken()按照phyint的规则解析,将信息已结构体struct vifconfig的形式保存给指针tmpPtr,再通过 currPtr 串到vifconf中
        if(strcmp("phyint", token)==0) {   //当前令牌是 "phyint",即标识了一个物理接口配置。
            //用于解析物理接口的配置,然后将结果存储在 tmpPtr 中。
            tmpPtr = parsePhyintToken();  
        
            if(tmpPtr == NULL) {
             //如果 parsePhyintToken 返回 NULL,表示解析失败,会输出错误消息并返回 0。
                closeConfigFile();
                my_log(LOG_WARNING, 0, "Unknown token '%s' in configfile", token);
                return 0;
            } else {

                // Insert config, and move temppointer to next location...
                *currPtr = tmpPtr;  //输出物理接口的各项配置参数,如接口名、速率限制、阈值等,然后将这些配置插入到链表中。
                currPtr = &tmpPtr->next;
            }
        }
        else if(strcmp("quickleave", token)==0) { 
//读取到的是quickleave,则写到公共配置中 commonConfig.fastUpstreamLeave = 1; 
            // Got a quickleave token....
            my_log(LOG_DEBUG, 0, "Config: Quick leave mode enabled.");
            commonConfig.fastUpstreamLeave = 1;

            // Read next token...
            token = nextConfigToken();
            continue;
        }
        else if(strcmp("hashtablesize", token)==0) {}
        else if(strcmp("defaultdown", token)==0) {
            // Got a defaultdown token...
            my_log(LOG_DEBUG, 0, "Config: interface Default as down stream.");
            commonConfig.defaultInterfaceState = IF_STATE_DOWNSTREAM;

            // Read next token...
            token = nextConfigToken();
            continue;
        }
        else if(strcmp("rescanvif", token)==0) {...}
        else if(strcmp("chroot", token)==0) {...}
        else if(strcmp("user", token)==0) {...}  
     token = getCurrentConfigToken();
  }
     closeConfigFile();
     return 1;
}

struct vifconfig {
    char*               name;//端口名称 如eth0
    short               state; //端口状态  0:无效端口  1:upstream 2:downstream
    int                 ratelimit;//访问速率的限制值
    int                 threshold; //TTl阈值  TTL(Time To Live)TTL的主要目的是控制数据包在网络中的生存时间。每当数据包经过一个网络路由器或交换机时,其TTL值减1
    struct SubnetList*  allowednets; //子网IP地址及子网掩码的结构体
    struct SubnetList*  allowedgroups;
    struct vifconfig*   next;  //下一个节点
};

第四步:初始化IGMP代理:调用igmpProxyInit函数进行初始化。

if ( !igmpProxyInit() ) {  
    my_log(LOG_ERR, 0, "Unable to initialize IGMPproxy.");
    break;
}
igmpProxyInit()

        初始化IGMP代理。设置信号处理程序、加载配置文件、构建物理网络接口列表,配置接口状态,并启用MRouter。

int igmpProxyInit(void) {
    struct sigaction sa;
    int Err;
    
    /**********1、信号处理器设置:**********/
    sa.sa_handler = signalHandler;
    sa.sa_flags = 0;    /* Interrupt system calls */
    sigemptyset(&sa.sa_mask);
    sigaction(SIGTERM, &sa, NULL);  //设置了两个信号处理程序 ,用于终止程序。当收到这些信号时,它们将传递给名为signalHandler的函数来处理。
    sigaction(SIGINT, &sa, NULL);

    /**********2、物理网络接口配置加载:**********/ 在多播通信中,物理接口(通常是网络接口卡)
    // Loads configuration for Physical interfaces...
    buildIfVc();   //用于加载和配置这些物理接口的信息。包括接口的名称、速率限制(ratelimit)、阈值(threshold)、状态(state)等

    /**********3、配置文件中的信息加载:**********/
    // Configures IF states and settings
    configureVifs(); //用于配置接口状态。多播通信可能涉及到多个接口,有些可能是上游接口(IF_STATE_UPSTREAM),有些可能是禁用的。

    /**********4、启用MRouter:**********/
    switch ( Err = enableMRouter() ) { //MRouter是用于构建多播树以正确传输多播数据包的一部分。函数会返回不同的错误代码,例如EADDRINUSE表示MC-Router API已经在使用。如果没有错误,代表MRouter初始化成功。
    case 0: break;
    case EADDRINUSE: my_log( LOG_ERR, EADDRINUSE, "MC-Router API already in use" ); break;
    default: my_log( LOG_ERR, Err, "MRT_INIT failed" );
    }


    /**********5、创建虚拟接口(VIFs):**********/
    //创建多播接口,将它们与物理接口相关联。如果有物理接口被配置为上游(upstream),则会记录下这些接口。
    //此外,对于非禁用的接口,将使用addVIF函数将它们添加到VIF列表中,以便多播数据包能够正确传输。如果没有上游接口,则会发出错误消息。
    /* create VIFs for all IP, non-loop interfaces
     */
    {
        unsigned Ix;
        struct IfDesc *Dp;
        int     vifcount = 0, upsvifcount = 0;

        // init array to "not set"
        for ( Ix = 0; Ix < MAX_UPS_VIFS; Ix++)
        {
            upStreamIfIdx[Ix] = -1;
        }

        for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {

            if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {
                if(Dp->state == IF_STATE_UPSTREAM) {
                    if (upsvifcount < MAX_UPS_VIFS -1)
                    {
                        my_log(LOG_DEBUG, 0, "Found upstrem IF #%d, will assing as upstream Vif %d",
                            upsvifcount, Ix);
                        upStreamIfIdx[upsvifcount++] = Ix;
                    } else {
                        my_log(LOG_ERR, 0, "Cannot set VIF #%d as upstream as well. Mac upstream Vif count is %d",
                            Ix, MAX_UPS_VIFS);
                    }
                }
        /**********6、:**********/
                if (Dp->state != IF_STATE_DISABLED) {
                    addVIF( Dp );
                    vifcount++;
                }
            }
        }

        if(0 == upsvifcount) {
            my_log(LOG_ERR, 0, "There must be at least 1 Vif as upstream.");
        }
    }

   
    // 初始化igmp数据包
    initIgmp();
    //  初始化路由表  
    initRouteTable();
    // Initialize timer
    callout_init();

    return 1;
}
buildIfVc();

      用于物理网络接口载入配置, 将物理网络接口的配置保存在全局变量 IfDescVc[ MAX_IF ]中。

struct ifreq {
    char ifr_name[IFNAMSIZ];  // 网络接口名称,如 "eth0"  "wlan0"
    union {
        struct sockaddr ifru_addr; 网络地址
        struct sockaddr ifru_dstaddr;目的地址
        struct sockaddr ifru_broadaddr;广播地址
        struct ifreq ifru_flags;接口的状态标志
        ... 
    } ifr_ifru;
};

struct IfDesc {
    char                Name[ sizeof( ((struct ifreq *)NULL)->ifr_name ) ];
    struct in_addr      InAdr;          /* == 0 for non IP interfaces */            
    short               Flags;
    short               state;        //如upstream、downstream
    struct SubnetList*  allowednets; //一个子网链表
    unsigned int        robustness;
    unsigned char       threshold;   /* ttl limit */
    unsigned int        ratelimit; 
    unsigned int        index;
};

struct IfDesc IfDescVc[ MAX_IF ], *IfDescEp = IfDescVc;
void  buildIfVc(void) {
    struct ifreq IfVc[ sizeof( IfDescVc ) / sizeof( IfDescVc[ 0 ] )  ];
    struct ifreq *IfEp;
    struct Config *config = getCommonConfig();

    int Sock;
/***********************1、打开网络套接字******************************/
    if ( (Sock = socket( AF_INET, SOCK_DGRAM, 0 )) < 0 ) //用于建立基于UDP数据包的网络套接字,类型为 SOCK_DGRAM,用于底层数据包处理。 0,表示操作系统会自动选择适当的协议。
 // 这个套接字将用于后续的网络配置查询。

/***********************2、获取网络接口信息:******************************/
    {
        struct ifconf IoCtlReq;  //定义一个结构体 IoCtlReq 用于存储ioctl的请求参数。

        IoCtlReq.ifc_buf = (void *)IfVc;  //设置 ifc_buf 成员为 IfVc 数组,以便在 ioctl 调用中获取接口信息。
        IoCtlReq.ifc_len = sizeof( IfVc );  //设置 ifc_len 成员为 IfVc 数组的大小,以指示要获取的接口信息的长度。

        if ( ioctl( Sock, SIOCGIFCONF, &IoCtlReq ) < 0 ) //使用 ioctl 调用来获取系统中的接口列表和相关配置信息。
            my_log( LOG_ERR, errno, "ioctl SIOCGIFCONF" );  //SIOCGIFCONF 是一个 ioctl 命令,用于获取系统上的接口列表。这将填充 IfVc 数组以及返回的 IfEp 指针,指示已填充数组的尾部。

        IfEp = (void *)((char *)IfVc + IoCtlReq.ifc_len);  //计算指向接口信息数组结束的指针 IfEp。
    }

    /* 循环遍历接口并将接口信息复制到IfDescVc*/
    {
        struct ifreq  *IfPt, *IfNext;

        // Temp keepers of interface params...
        uint32_t addr, subnet, mask;
/***********************3、遍历接口并获取信息:******************************/
        for ( IfPt = IfVc; IfPt < IfEp; IfPt = IfNext ) { //该循环体会调用将前面获取的 接口名字多次使用传入ioctl来分别获得子网掩码、索引號、Flags等信息。
            struct ifreq IfReq;     //函数循环遍历 IfVc 数组,获取每个接口的信息
            char FmtBu[ 32 ];

            IfNext = (struct ifreq *)((char *)&IfPt->ifr_addr +
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
                    IfPt->ifr_addr.sa_len
#else
                    sizeof(struct sockaddr_in)
#endif
            );
            if (IfNext < IfPt + 1)
                IfNext = IfPt + 1;

            strncpy( IfDescEp->Name, IfPt->ifr_name, sizeof( IfDescEp->Name ) );       //将接口的名称复制到 IfDescEp 结构体的 Name 成员中。
 
            // Currently don't set any allowed nets...
            //IfDescEp->allowednets = NULL;

            // Set the index to -1 by default.
            IfDescEp->index = (unsigned int)-1;

            /* don't retrieve more info for non-IP interfaces
             */
            if ( IfPt->ifr_addr.sa_family != AF_INET ) { //检查接口的地址家族是否为 IPv4。如果不是,将 IfDescEp 结构体的 InAdr.s_addr 成员设置为 0,表示这是一个非 IPv4 接口。
                IfDescEp->InAdr.s_addr = 0;  /* mark as non-IP interface */
                IfDescEp++;
                continue;
            }

            // Get the interface adress...
            IfDescEp->InAdr.s_addr = s_addr_from_sockaddr(&IfPt->ifr_addr); //如果接口是 IPv4 接口,将其 IP 地址保存在 IfDescEp 结构体的 InAdr.s_addr 成员中。
            addr = IfDescEp->InAdr.s_addr;

            memcpy( IfReq.ifr_name, IfDescEp->Name, sizeof( IfReq.ifr_name ) );  //将接口名称复制到 IfReq 结构体的 ifr_name 成员中,用于后续的 ioctl 调用。

            if (ioctl(Sock, SIOCGIFINDEX, &IfReq ) < 0) //使用 ioctl 获取接口的索引号,并将它保存在 IfDescEp 结构体的 ifIndex 成员中。
                my_log(LOG_ERR, errno, "ioctl SIOCGIFINDEX for %s", IfReq.ifr_name);
            IfDescEp->ifIndex = IfReq.ifr_ifindex;

            // Get the subnet mask...
            if (ioctl(Sock, SIOCGIFNETMASK, &IfReq ) < 0) //使用 ioctl 获取接口的子网掩码,并将它保存在 mask 和 subnet 变量中。
                my_log(LOG_ERR, errno, "ioctl SIOCGIFNETMASK for %s", IfReq.ifr_name);
            mask = s_addr_from_sockaddr(&IfReq.ifr_addr); // Do not use ifr_netmask as it is not available on freebsd
            subnet = addr & mask;

            if ( ioctl( Sock, SIOCGIFFLAGS, &IfReq ) < 0 )  //使用 ioctl 获取接口的标志(flags),并将它们保存在 IfDescEp 结构体的 Flags 成员中。
                my_log( LOG_ERR, errno, "ioctl SIOCGIFFLAGS" );

            IfDescEp->Flags = IfReq.ifr_flags;

            // aimwang: when pppx get dstaddr for use
            if (0x10d1 == IfDescEp->Flags) //检查接口的标志是否满足某个条件,如果是,则使用 ioctl 获取接口的目的地址,并进行相关处理。
            {
                if ( ioctl( Sock, SIOCGIFDSTADDR, &IfReq ) < 0 )
                    my_log(LOG_ERR, errno, "ioctl SIOCGIFDSTADDR for %s", IfReq.ifr_name);
                addr = s_addr_from_sockaddr(&IfReq.ifr_dstaddr);
                subnet = addr & mask;
            }

            // 为接口的 allowednets 成员分配内存,以存储子网信息
            IfDescEp->allowednets = (struct SubnetList *)malloc(sizeof(struct SubnetList));
            if(IfDescEp->allowednets == NULL) my_log(LOG_ERR, 0, "Out of memory !");

            // Create the network address for the IF..
            IfDescEp->allowednets->next = NULL;
            IfDescEp->allowednets->subnet_mask = mask;
            IfDescEp->allowednets->subnet_addr = subnet;

            // 设置 IfDescEp 结构体的其他默认参数。
            IfDescEp->state         = config->defaultInterfaceState;
            IfDescEp->robustness    = DEFAULT_ROBUSTNESS;
            IfDescEp->threshold     = DEFAULT_THRESHOLD;   /* ttl limit */
            IfDescEp->ratelimit     = DEFAULT_RATELIMIT;

            // 记录接口的详细信息,如名称、索引、IP地址、标志等。
            my_log( LOG_DEBUG, 0, "buildIfVc: Interface %s Index: %d Addr: %s, Flags: 0x%04x, Network: %s",
                 IfDescEp->Name,
                 IfDescEp->ifIndex,
                 fmtInAdr( FmtBu, IfDescEp->InAdr ),
                 IfDescEp->Flags,
                 inetFmts(subnet,mask, s1));

            IfDescEp++;
        }
    }

    close( Sock );//记录接口的详细信息,如名称、索引、IP地址、标志等。
}
configureVifs();

         用于配置接口状态。buildIfVc()函数将物理网络接口配置保存在IfDescVc[ MAX_IF ]中,configuireVifs()的任务就是将配置文件中关于虚拟网络设备的诸如threshold、allowednets、ratelimit等(config文件igmpProxy.conf中的配置)信息加载到 IfDescVc[ MAX_IF ] 中。

void configureVifs(void) {
    unsigned Ix;
    struct IfDesc *Dp;
    struct vifconfig *confPtr;

    // If no config is available, just return...
    if(vifconf == NULL) {
        return;
    }

    // Loop through all VIFs...
    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
        if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {

            // Now try to find a matching config...
            for( confPtr = vifconf; confPtr; confPtr = confPtr->next) {

                // I the VIF names match...
                if(strcmp(Dp->Name, confPtr->name)==0) {
                    struct SubnetList *vifLast;

                    my_log(LOG_DEBUG, 0, "Found config for %s", Dp->Name);


                    // Set the VIF state
                    Dp->state = confPtr->state;

                    Dp->threshold = confPtr->threshold;
                    Dp->ratelimit = confPtr->ratelimit;

                    // Go to last allowed net on VIF...
                    for(vifLast = Dp->allowednets; vifLast->next; vifLast = vifLast->next);

                    // Insert the configured nets...
                    vifLast->next = confPtr->allowednets;

                    Dp->allowedgroups = confPtr->allowedgroups;

                    break;
                }
            }
        }
    }
}
enableMRouter

         完成mrouted API初始化,激活mrouted模块

int enableMRouter(void)
{
    int Va = 1;
    //打开套接口MRouterFD
    if ( (MRouterFD  = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP)) < 0 )
        my_log( LOG_ERR, errno, "IGMP socket open" );
    //使用setsockopt函数,通过指定 IPPROTO_IP 和 MRT_INIT 选项,将套接字 MRouterFD 配置为   支持IP协议的多播路由功能。MRT_INIT 选项用于激活 Linux 内核中的多播路由服务,以支持 IGMP
    if ( setsockopt( MRouterFD, IPPROTO_IP, MRT_INIT,
                     (void *)&Va, sizeof( Va ) ) ) 
        return errno;

    return 0;
}
addVIF( Dp )

         执行完上面步骤,接下来就在内核中创建VIF虚拟设备,通过轮询 IfDescVc[ MAX_IF ] 中的信息for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ),并用addVIF函数添加虚拟设备。

void addVIF( struct IfDesc *IfDp )
{
    struct vifctl VifCtl;
    struct VifDesc *VifDp;

    /* search free (aimwang: or exist) VifDesc
     */
    for ( VifDp = VifDescVc; VifDp < VCEP( VifDescVc ); VifDp++ ) {
        if ( ! VifDp->IfDp || VifDp->IfDp == IfDp)
            break;
    }

    /* no more space
     */
    if ( VifDp >= VCEP( VifDescVc ) )
        my_log( LOG_ERR, ENOMEM, "addVIF, out of VIF space" );

    ......

    if ( setsockopt( MRouterFD, IPPROTO_IP, MRT_ADD_VIF,
                     (char *)&VifCtl, sizeof( VifCtl ) ) )//这是针对 IGMP 路由(MRouter)的设置。MRT_ADD_VIF 选项用于添加一个虚拟接口 (VIF) 到 MRouter。
  //MRouterFD 是原始套接字,用于与 IGMP 路由相关的底层操作。
  //VifCtl 包含有关虚拟接口的信息,例如接口索引、地址和其他参数。
  //这个设置通常用于配置 MRouter 来处理多播路由。
}
initIgmp();

         初始化igmp数据包的报文格式,创建发送和接收数据包的缓冲区,为之后发送查询和接收报告做准备。实际是将ip包首部写入IGMP相关信息

void initIgmp(void) {
    struct ip *ip;

    //分配接收和发送缓冲区,存储接收到的 IGMP 数据包和准备要发送的 IGMP 数据包
    recv_buf = malloc(RECV_BUF_SIZE); //8192个字节
    send_buf = malloc(RECV_BUF_SIZE); 

    k_hdr_include(true);    //通知内核在发送数据时要包括 IP 头。通常,当使用原始套接字发送数据包时,应该包括 IP 头。
    k_set_rcvbuf(256*1024,48*1024); //设置接收缓冲区的大小。它指定了接收缓冲区的上限和下限大小。这允许套接字接收和缓冲更多的数据
    k_set_ttl(1);   //设置 TTL(Time to Live)的值为1。这限制多播数据包在单一跳上进行传输。在 TTL 为1的情况下,多播数据包不能跨越多个网络或路由器,只在本地网络内传输。
    k_set_loop(false);  //禁用多播数据包的回送(loopback)。当禁用回送时,发送到多播组的数据包不会被发送者接收。通常,回送是默认启用的。

    ip         = (struct ip *)send_buf;
    memset(ip, 0, sizeof(struct ip));
    /*
     * Fields zeroed that aren't filled in later:
     * - IP ID (let the kernel fill it in)
     * - Offset (we don't send fragments)
     * - Checksum (let the kernel fill it in)
     */  //初始化 IP 头:这部分代码初始化 IP 头的字段,包括版本、首部长度、服务类型、TTL 等。此头通常放在发送缓冲区中,以便发送 IGMP 数据包时附带 IP 头信息。
    ip->ip_v   = IPVERSION;
    ip->ip_hl  = (sizeof(struct ip) + 4) >> 2; /*  +4为路由器警报选项*/
    ip->ip_tos = 0xc0;      /* Internet Control */
    ip->ip_ttl = MAXTTL;    /* applies to unicasts only */
    ip->ip_p   = IPPROTO_IGMP;

    allhosts_group   = htonl(INADDR_ALLHOSTS_GROUP); //224.0.0.1 在本子网上的全部參加多播的主机和路由器;IP 多播组地址,通常用于标识所有主机(所有参与多播通信的主机)。IPv4 中的所有主机都会加入此组以接收多播数据。allhosts_group 保存了这个多播组地址的网络字节序(big-endian)表示。
    allrouters_group = htonl(INADDR_ALLRTRS_GROUP);//224.0.0.2 在本子网上的全部參加多播的路由器;另一个 IP 多播组地址,通常用于标识所有路由器(所有参与多播通信的路由器)。路由器通常会加入此组,以便参与多播路由协议。
    alligmp3_group   = htonl(INADDR_ALLIGMPV3_GROUP);//INADDR_ALLIGMPV3_GROUP:这是用于 IGMP 版本 3 的多播组地址。IGMPv3 支持源特定的多播,允许主机根据数据流选择要接收的数据。
}
initRouteTable();

初始化路由表,通过轮询 IfDescVc[ MAX_IF ] 中的信息,如果是downstream上的VIF设备,为每一个下行接口加入多播组allrouters_group

struct RouteTable {
    struct RouteTable   *nextroute;     // Pointer to the next group in line.
    struct RouteTable   *prevroute;     // Pointer to the previous group in line.
    uint32_t            group;          // The group to route
    uint32_t            originAddrs[MAX_ORIGINS]; // The origin adresses (only set on activated routes)
    uint32_t            vifBits;        // Bits representing recieving VIFs.

    // Keeps the upstream membership state...
    short               upstrState;     // Upstream membership state.
    int                 upstrVif;       // Upstream Vif Index.

    // These parameters contain aging details.
    uint32_t            ageVifBits;     // Bits representing aging VIFs.
    int                 ageValue;       // Downcounter for death.
    int                 ageActivity;    // Records any acitivity that notes there are still listeners.

    // Keeps downstream hosts information
    uint32_t            downstreamHostsHashSeed;
    uint8_t             downstreamHostsHashTable[];
};

// Keeper for the routing table...
static struct RouteTable   *routing_table;
void initRouteTable(void) {//完成初始化步骤,路由器就能够开始接收和处理多播数据,
    unsigned Ix;
    struct IfDesc *Dp;

    //清空路由表,将其初始化为 NULL。这个表将用于存储多播路由信息。
    routing_table = NULL;

    // Join the all routers group on downstream vifs...
    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) { //加入多播组:然后,遍历所有接口 Dp
        // If this is a downstream vif, we should join the All routers group...
        if( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) && Dp->state == IF_STATE_DOWNSTREAM) {
            //如果 Dp 是一个下行接口,并且它不是回环接口,则加入特定的多播组。
            //这里加入的两个多播组分别是 allrouters_group 和 alligmp3_group。
            k_join(Dp, allrouters_group);

            k_join(Dp, alligmp3_group);
        }
    }
}
callout_init(); 

初始化时间表。初始化询问超时,queue=NULL

void callout_init(void) {
    queue = NULL;
}

第五步:主循环:调用igmpProxyRun函数进入IGMP代理的主循环。

igmpProxyRun(); //IGMP代理的主循环
igmpProxyRun()
    主程序的运行循环,通过该循环来维护多播组的成员信息,并与其他多播路由器和组成员保持同步。使用 pselect() 函数等待套接字上的数据包,并处理它们。主要目的是等待数据的到达、处理收到的 IGMP 请求,以及处理定时器事件。
1、sendGeneralMembershipQuery();向所有allhosts_group主机定时发送IGMP通用查询。
2、进入循环,当有信号中断,才会退出循环
3、每次循环重新设置定时器timeout。之后告知内核等待接收Rt = pselect( MaxFD +1, &ReadFDS, NULL, NULL, timeout, NULL );
4、当有IGMP报文来时,通过recvfrom接收IGMP报文数据并存入recv_buf
5、运行accptIgmp()处理IGMP报文
6、处理完IGMP报文后退出系统,然后返回值循环开始的地方,进行下一次的IGMP的报文。
void igmpProxyRun(void) {
    struct Config *config = getCommonConfig(); //获取配置:首先,它获取 IGMP Proxy 的配置信息。
    // Set some needed values.
    register int recvlen;
    int     MaxFD, Rt, secs;
    fd_set  ReadFDS;
    socklen_t dummy = 0;
    struct  timespec  curtime, lasttime, difftime, tv;
    // The timeout is a pointer in order to set it to NULL if nessecary.
    struct  timespec  *timeout = &tv;

    // Initialize timer vars
    difftime.tv_nsec = 0;
    clock_gettime(CLOCK_MONOTONIC, &curtime); //初始化定时器:初始化定时器相关的变量。这些变量用于管理 IGMP 组的生存时间。
    lasttime = curtime;

    // First thing we send a membership query in downstream VIF's...
    sendGeneralMembershipQuery();  //发送查询:调用 sendGeneralMembershipQuery 函数,在下游虚拟接口(VIFs)上发送一般的成员查询。

    // Loop until the end...
    for (;;) {//进入无限循环,直到收到中断信号(如 SIGINT)

        // Process signaling...
        if (sighandled) {//处理信号:检查是否有信号需要处理,如果收到中断信号,则退出循环,否则继续。
            if (sighandled & GOT_SIGINT) {
                sighandled &= ~GOT_SIGINT;
                my_log(LOG_NOTICE, 0, "Got a interrupt signal. Exiting.");
                break;
            }
        }

        /* aimwang: call rebuildIfVc */
        if (config->rescanVif)//重新扫描 VIFs:如果配置中设置了 rescanVif,则调用 rebuildIfVc 函数。这个函数的作用是重新扫描物理网络接口,以便在配置文件中加载新的信息。
            rebuildIfVc();

        // Prepare timeout...
        secs = timer_nextTimer();//准备超时:计算下一个定时器的触发时间,并设置 timeout 以等待一段时间。如果没有等待超时事件,则将 timeout 设置为 NULL。
        if(secs == -1) {
            timeout = NULL;
        } else {
            timeout->tv_nsec = 0;
            timeout->tv_sec = (secs > 3) ? 3 : secs; // aimwang: set max timeout
        }

        // Prepare for select.
        MaxFD = MRouterFD;  //准备 select:设置 MaxFD 为 IGMP 套接字的文件描述符,清空 ReadFDS 并将 IGMP 套接字加入 ReadFDS 集合中,以准备进行 select 调用。

        FD_ZERO( &ReadFDS );
        FD_SET( MRouterFD, &ReadFDS );

        // wait for input
        Rt = pselect( MaxFD +1, &ReadFDS, NULL, NULL, timeout, NULL );//等待输入:调用 pselect 函数,等待套接字上的数据到达或直到超时。pselect 允许在等待期间捕获信号,这有助于及时处理信号。

        // log and ignore failures
        if( Rt < 0 ) {
            my_log( LOG_WARNING, errno, "select() failure" );
            continue;
        }
        else if( Rt > 0 ) {//取 IGMP 请求:如果 select 返回值 Rt 大于0,表示有数据到达,则读取从 MRouterFD 套接字接收的 IGMP 请求,然后调用 acceptIgmp 函数处理这些请求。

            // Read IGMP request, and handle it...
            if( FD_ISSET( MRouterFD, &ReadFDS ) ) {

                recvlen = recvfrom(MRouterFD, recv_buf, RECV_BUF_SIZE,
                                   0, NULL, &dummy);
                if (recvlen < 0) {
                    if (errno != EINTR) my_log(LOG_ERR, errno, "recvfrom");
                    continue;
                }

                acceptIgmp(recvlen);
            }
        }

        // At this point, we can handle timeouts...
        do { //处理超时:在循环的最后,处理定时器超时。根据不同的超时事件,调用 age_callout_queue 来处理队列中的超时事件。
            /*
             * If the select timed out, then there's no other
             * activity to account for and we don't need to
             * call gettimeofday.
             */
            if (Rt == 0) {
                curtime.tv_sec = lasttime.tv_sec + secs;
                curtime.tv_nsec = lasttime.tv_nsec;
                Rt = -1; /* don't do this next time through the loop */
            } else {
                clock_gettime(CLOCK_MONOTONIC, &curtime);
            }
            difftime.tv_sec = curtime.tv_sec - lasttime.tv_sec;
            difftime.tv_nsec += curtime.tv_nsec - lasttime.tv_nsec;
            while (difftime.tv_nsec > 1000000000) {
                difftime.tv_sec++;
                difftime.tv_nsec -= 1000000000;
            }
            if (difftime.tv_nsec < 0) {
                difftime.tv_sec--;
                difftime.tv_nsec += 1000000000;
            }
            lasttime = curtime;
            if (secs == 0 || difftime.tv_sec > 0)
                age_callout_queue(difftime.tv_sec);
            secs = -1;
        } while (difftime.tv_sec > 0);
    }
}
sendGeneralMembershipQuery();

       获取通用配置,向所有allhosts_group主机定时发送IGMP通用查询,并进行定时器设置

void sendGeneralMembershipQuery(void) {
    struct  Config  *conf = getCommonConfig();
    struct  IfDesc  *Dp;
    int             Ix;

    // Loop through all downstream vifs...
    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
        if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {
            if(Dp->state == IF_STATE_DOWNSTREAM) {
                // Send the membership query...
                sendIgmp(Dp->InAdr.s_addr, allhosts_group,
                   GMP_MEMBERSHIP_QUERY,
                   conf->queryResponseInterval * IGMP_TIMER_SCALE, 0, 0, Dp->ifIndex);

                my_log(LOG_DEBUG, 0,
                    "Sent membership query from %s ifIndex %d to %s. Delay: %d",
                    inetFmt(Dp->InAdr.s_addr,s1),
                    Dp->ifIndex,
                    inetFmt(allhosts_group,s2),
                    conf->queryResponseInterval);
            }
        }
    }

   //设置定时器,周期性发送IGMP查询并更新路由表
    // Install timer for aging active routes.路由表更新定时器设置为queryResponseInterval(10ms)
    timer_setTimer(conf->queryResponseInterval, (timer_f)ageActiveRoutes, NULL);

    // Install timer for next general query...
    if(conf->startupQueryCount>0) { //开启时,startupQueryInterval(125/4ms)时间发送,发送次数startupQueryCount(2),每发送一次IGMP报文startupQueryCount--
        // Use quick timer...
        timer_setTimer(conf->startupQueryInterval, (timer_f)sendGeneralMembershipQuery, NULL);
        // Decrease startup counter...
        conf->startupQueryCount--;
    }
    else { //当startupQueryCount不大于0时采用queryInterval(125ms)时间来发送报文。
        // Use slow timer...
        timer_setTimer(conf->queryInterval, (timer_f)sendGeneralMembershipQuery, NULL);
    }
}
sendIgmp
void sendIgmp(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen, int ifidx) {
    struct sockaddr_in sdst;
    int setloop = 0, setigmpsource = 0;

    buildIgmp(src, dst, type, code, group, datalen); //获取原地址、目的地址、类型、组地址、数据长度等信息封装成IGMP报文并存放在send_buf中

    if (IN_MULTICAST(ntohl(dst))) {//如果des目的地址是一个有效D类地址
        k_set_if(src, ifidx); //设定一个组播的外出接口
        setigmpsource = 1;
        if (type != IGMP_DVMRP || dst == allhosts_group) {//开启回环地址
            setloop = 1;
            k_set_loop(true);
        }
    }

    memset(&sdst, 0, sizeof(sdst));
    sdst.sin_family = AF_INET;
#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
    sdst.sin_len = sizeof(sdst);
#endif
    sdst.sin_addr.s_addr = dst;
    //将IGMP报文发出去
    if (sendto(MRouterFD, send_buf,
               IP_HEADER_RAOPT_LEN + IGMP_MINLEN + datalen, 0,
               (struct sockaddr *)&sdst, sizeof(sdst)) < 0) {
        if (errno == ENETDOWN)
            my_log(LOG_ERR, errno, "Sender VIF was down.");
        else
            my_log(LOG_INFO, errno,
                "sendto to %s on %s",
                inetFmt(dst, s1), inetFmt(src, s2));
    }

    if(setigmpsource) {
        if (setloop) {
            k_set_loop(false);
        }
        // Restore original...
        k_set_if(INADDR_ANY, 0);
    }

    my_log(LOG_DEBUG, 0, "SENT %s from %-15s to %s",
        igmpPacketKind(type, code),
        src == INADDR_ANY ? "INADDR_ANY" : inetFmt(src, s1), inetFmt(dst, s2));
}
buildIgmp 

        代码中可以看出发送IGMP包时需要带IP头部。主要内容是获取原地址、目的地址、类型、组地址、数据长度等信息封装成IGMP报文并存放在send_buf中

static void buildIgmp(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) {
    struct ip *ip;
    struct igmp *igmp;
    extern int curttl;

    ip                      = (struct ip *)send_buf;
    ip->ip_src.s_addr       = src;
    ip->ip_dst.s_addr       = dst;
    ip_set_len(ip, IP_HEADER_RAOPT_LEN + IGMP_MINLEN + datalen);

    if (IN_MULTICAST(ntohl(dst))) {
        ip->ip_ttl = curttl;
    } else {
        ip->ip_ttl = MAXTTL;
    }

    /* Add Router Alert option */
    ((unsigned char*)send_buf+MIN_IP_HEADER_LEN)[0] = IPOPT_RA;
    ((unsigned char*)send_buf+MIN_IP_HEADER_LEN)[1] = 0x04;
    ((unsigned char*)send_buf+MIN_IP_HEADER_LEN)[2] = 0x00;
    ((unsigned char*)send_buf+MIN_IP_HEADER_LEN)[3] = 0x00;

    igmp                    = (struct igmp *)(send_buf + IP_HEADER_RAOPT_LEN);
    igmp->igmp_type         = type;
    igmp->igmp_code         = code;
    igmp->igmp_group.s_addr = group;
    igmp->igmp_cksum        = 0;
    igmp->igmp_cksum        = inetChksum((unsigned short *)igmp,
                                         IP_HEADER_RAOPT_LEN + datalen);
}
acceptIgmp(recvlen);

     处理新收到的位于输入报文缓冲区中的IGMP报文。上面已经已经知道接收到的buf是封装了的IGMP信息的IP数据包,通过(struct ip *)recv_buf得到IP头,原地址,目的地址,并激活创建新的路由表。如果IP版本号不为0,说明这是来自外部接口的报文,根据recv_buf中的数据指向的结构体igmp的igmp_type分类处理消息。

void acceptIgmp(int recvlen) {
    register uint32_t src, dst, group;
    struct ip *ip;
    struct igmp *igmp;
    struct igmpv3_report *igmpv3;
    struct igmpv3_grec *grec;
    int ipdatalen, iphdrlen, ngrec, nsrcs, i;
    /******************1、解析 IP 数据包:********************/
    //首先,代码检查接收到的数据包长度是否足够包含 IP 头。如果长度不够,输出警告并返回。
    if (recvlen < (int)sizeof(struct ip)) {
        my_log(LOG_WARNING, 0,
            "received packet too short (%u bytes) for IP header", recvlen);
        return;
    }

    //然后,解析 IP 头,获取源地址 (src)、目标地址 (dst)。
    ip        = (struct ip *)recv_buf;
    src       = ip->ip_src.s_addr;
    dst       = ip->ip_dst.s_addr;

    /******************2、处理内核请求(Kernel Request):********************/
    /*
     * this is most likely a message from the kernel indicating that
     * a new src grp pair message has arrived and so, it would be
     * necessary to install a route into the kernel for this.
     */
    //如果 IP 协议字段 (ip_p) 为 0,通常表示这是一个内核请求。
    if (ip->ip_p == 0) {
        if (src == 0 || dst == 0) {//代码检查源地址和目标地址,
            my_log(LOG_WARNING, 0, "kernel request not accurate");
        }
        else {
            struct IfDesc *checkVIF;

            for(i=0; i<MAX_UPS_VIFS; i++) //然后遍历已配置的上行(Upstream)VIF(Virtual Interface)列表。
            {
                if(-1 != upStreamIfIdx[i])
                {
                    // Check if the source address matches a valid address on upstream vif.
                    checkVIF = getIfByIx( upStreamIfIdx[i] ); //如果源地址匹配某个上行 VIF,说明这是一个内核请求,代码尝试激活路由。
                    if(checkVIF == 0) {
                        my_log(LOG_ERR, 0, "Upstream VIF was null.");
                        return;
                    }
                    else if(src == checkVIF->InAdr.s_addr) {
                        my_log(LOG_NOTICE, 0, "Route activation request from %s for %s is from myself. Ignoring.",
                            inetFmt(src, s1), inetFmt(dst, s2));
                        return;
                    }
                    else if(!isAdressValidForIf(checkVIF, src)) { //
                        struct IfDesc *downVIF = getIfByAddress(src);
                        if (downVIF && downVIF->state & IF_STATE_DOWNSTREAM) {
                            my_log(LOG_NOTICE, 0, "The source address %s for group %s is from downstream VIF[%d]. Ignoring.",
                                inetFmt(src, s1), inetFmt(dst, s2), i);
                        } else {
                            my_log(LOG_WARNING, 0, "The source address %s for group %s, is not in any valid net for upstream VIF[%d].",
                                inetFmt(src, s1), inetFmt(dst, s2), i);
                        }
                    } else {
                        // Activate the route.
                        int vifindex = checkVIF->index;
                        my_log(LOG_DEBUG, 0, "Route activate request from %s to %s on VIF[%d]",
                            inetFmt(src,s1), inetFmt(dst,s2), vifindex);
                        activateRoute(dst, src, vifindex);
                        i = MAX_UPS_VIFS;
                    }
                } else {
                    i = MAX_UPS_VIFS;
                }
            }
        }
        return;
    }

    
    //如果不是内核请求,继续处理 IGMP 报文。首先计算 IP 头的长度 (iphdrlen) 和 IP 数据部分的长度 (ipdatalen),
    iphdrlen  = ip->ip_hl << 2;
    ipdatalen = ip_data_len(ip);

    //然后检查数据包的长度是否符合预期。
    if (iphdrlen + ipdatalen != recvlen) {
        my_log(LOG_WARNING, 0,
            "received packet from %s shorter (%u bytes) than hdr+data length (%u+%u)",
            inetFmt(src, s1), recvlen, iphdrlen, ipdatalen);
        return;
    }

   

    /******************4、 解析 IGMPv3 报文::********************/
    //如果 IGMP 报文的类型是 IGMPv3,代码解析 IGMPv3 报文。它逐个处理组记录(Group Record),根据记录的类型执行相应的操作。
    igmp = (struct igmp *)(recv_buf + iphdrlen);
    if ((ipdatalen < IGMP_MINLEN) ||
        (igmp->igmp_type == IGMP_V3_MEMBERSHIP_REPORT && ipdatalen <= IGMPV3_MINLEN)) {
        my_log(LOG_WARNING, 0,
            "received IP data field too short (%u bytes) for IGMP, from %s",
            ipdatalen, inetFmt(src, s1));
        return;
    }

    my_log(LOG_NOTICE, 0, "RECV %s from %-15s to %s",
        igmpPacketKind(igmp->igmp_type, igmp->igmp_code),
        inetFmt(src, s1), inetFmt(dst, s2) );

    /******************5、处理 IGMP 报文类型::********************/
    switch (igmp->igmp_type) {
    case IGMP_V1_MEMBERSHIP_REPORT:
    case IGMP_V2_MEMBERSHIP_REPORT:
        group = igmp->igmp_group.s_addr;
        acceptGroupReport(src, group);
        return;

    case IGMP_V3_MEMBERSHIP_REPORT: //如果 IGMP 报文的类型是 IGMPv3,代码解析 IGMPv3 报文。它逐个处理组记录(Group Record),根据记录的类型执行相应的操作。
        igmpv3 = (struct igmpv3_report *)(recv_buf + iphdrlen);
        grec = &igmpv3->igmp_grec[0];
        ngrec = ntohs(igmpv3->igmp_ngrec);
        while (ngrec--) {
            if ((uint8_t *)igmpv3 + ipdatalen < (uint8_t *)grec + sizeof(*grec))
                break;
            group = grec->grec_mca.s_addr;
            nsrcs = ntohs(grec->grec_nsrcs);
            switch (grec->grec_type) {
            case IGMPV3_MODE_IS_INCLUDE:
            case IGMPV3_CHANGE_TO_INCLUDE:
                if (nsrcs == 0) {
                    acceptLeaveMessage(src, group);
                    break;
                } /* else fall through */
            case IGMPV3_MODE_IS_EXCLUDE:
            case IGMPV3_CHANGE_TO_EXCLUDE:
            case IGMPV3_ALLOW_NEW_SOURCES:
                acceptGroupReport(src, group);
                break;
            case IGMPV3_BLOCK_OLD_SOURCES:
                break;
            default:
                my_log(LOG_INFO, 0,
                    "ignoring unknown IGMPv3 group record type %x from %s to %s for %s",
                    grec->grec_type, inetFmt(src, s1), inetFmt(dst, s2),
                    inetFmt(group, s3));
                break;
            }
            grec = (struct igmpv3_grec *)
                (&grec->grec_src[nsrcs] + grec->grec_auxwords * 4);
        }
        return;

    case IGMP_V2_LEAVE_GROUP:
        group = igmp->igmp_group.s_addr;
        acceptLeaveMessage(src, group);
        return;

    case IGMP_MEMBERSHIP_QUERY: //对于 IGMP 查询消息 (IGMP_MEMBERSHIP_QUERY),代码直接返回,没有特殊处理逻辑。
        return;

    default: //日志包含 IGMP 报文的类型、源地址、目标地址等信息,以帮助调试和跟踪 IGMP 操作。
        my_log(LOG_INFO, 0,
            "ignoring unknown IGMP message type %x from %s to %s",
            igmp->igmp_type, inetFmt(src, s1),
            inetFmt(dst, s2));
        return;
    }
}

activateRoute(dst, src, vifindex);

函数用于激活或重新安装多播路由,用于管理多播组的成员资格和数据流的传输。如果组已经被激活,它将被重新安装到内核中。如果路由处于激活状态,则不需要输入originAddr。 

int activateRoute(uint32_t group, uint32_t originAddr, int upstrVif) {
    struct RouteTable*  croute;
    int result = 0;

    // Find the requested route.
    croute = findRoute(group); //函数首先尝试查找指定组的路由信息,如果找不到相应的路由信息,则插入新的路由信息。
    if(croute == NULL) {  //无信息时,将当前获得group信息加载到routing_table
        my_log(LOG_DEBUG, 0,
            "No table entry for %s [From: %s]. Inserting route.",
            inetFmt(group, s1),inetFmt(originAddr, s2));

        // Insert route, but no interfaces have yet requested it downstream.
        insertRoute(group, -1, 0); //因为此时还没收到downstream方向的igmp报告,只传-1表明只完成routing_table更新,还不能更新内核Mroute

        // Retrieve the route from table...
        croute = findRoute(group); 
    }

    if(croute != NULL) {//路由信息(croute != NULL)
    //检查 originAddr是否大于 0,如果是,则更新路由信息中的来源地址信息(upstream)。
        // If the origin address is set, update the route data.
        if(originAddr > 0) {
            // find this origin, or an unused slot
            int i;
        //originAddr 可以有多个来源,最多支持 MAX_ORIGINS 个来源,每个来源对应一个位置。如果已经有 MAX_ORIGINS 个来源,将会替换最早的来源。
            for (i = 0; i < MAX_ORIGINS; i++) {
                // unused slots are at the bottom, so we can't miss this origin
                if (croute->originAddrs[i] == originAddr || croute->originAddrs[i] == 0) {
                    break;
                }
            }

            if (i == MAX_ORIGINS) {
                i = MAX_ORIGINS - 1; 

                my_log(LOG_WARNING, 0, "Too many origins for route %s; replacing %s with %s",
                    inetFmt(croute->group, s1),
                    inetFmt(croute->originAddrs[i], s2),
                    inetFmt(originAddr, s3));
            }

            // set origin
            croute->originAddrs[i] = originAddr;

            // move it to the top
            while (i > 0) {
                uint32_t t = croute->originAddrs[i - 1];
                croute->originAddrs[i - 1] = croute->originAddrs[i];
                croute->originAddrs[i] = t;
                i--;
            } 
        }
        croute->upstrVif = upstrVif;  //将路由的upstrVif设置为提供的上游虚拟接口标识。

//(vifBits > 0),表明路由信息中存在定义虚拟设备,则更新内核中的路由表,并将croute更新到内核。这样,只有当路由信息被监听时,才会更新内核路由表。
        // Only update kernel table if there are listeners !
        if(croute->vifBits > 0) { 
            result = internUpdateKernelRoute(croute, 1);
        }
    }
    logRouteTable("Activate Route");

    return result;
}

acceptGroupReport(src, group);

1、检查 group 是否是有效的多播组地址

2、通过 getIfByAddress 函数找到与源地址 src 相关联的网络接口 sourceVif(与下行接口在同一子网)

3、进入inserRoute检测路由表是否存在,若不存在,则插入新的路由表

void acceptGroupReport(uint32_t src, uint32_t group) {//src 源地址,group多播组地址
    struct IfDesc  *sourceVif;

    // Sanitycheck the group adress...
    if(!IN_MULTICAST( ntohl(group) )) { //通过 IN_MULTICAST 宏检查 group 是否是有效的多播组地址。如果不是,会输出警告并返回。
        my_log(LOG_WARNING, 0, "The group address %s is not a valid Multicast group.",inetFmt(group, s1));
        return;
    }

    // Find the interface on which the report was received.
    sourceVif = getIfByAddress( src ); //通过 getIfByAddress 函数找到与源地址 src 相关联的网络接口 sourceVif。如果没有找到,会输出警告并返回。
    if(sourceVif == NULL) {
        my_log(LOG_WARNING, 0, "No interfaces found for source %s",
            inetFmt(src,s1));
        return;
    }

    if(sourceVif->InAdr.s_addr == src) {//检查源地址是否与 端口自身地址相同,如果是,则输出通知并返回,因为 IGMP 消息来自自身。
        my_log(LOG_NOTICE, 0, "The IGMP message was from myself. Ignoring.");
        return;
    }

    // We have a IF so check that it's an downstream IF.
    if(sourceVif->state == IF_STATE_DOWNSTREAM) {//检查 sourceVif 是否是一个下游接口(IF_STATE_DOWNSTREAM)。将组地址和该接口的虚拟设备号加载到内核中
        my_log(LOG_DEBUG, 0, "Should insert group %s (from: %s) to route table. Vif Ix : %d",
            inetFmt(group,s1), inetFmt(src,s2), sourceVif->index);

        // If we don't have a black- and whitelist we insertRoute and done
        if(sourceVif->allowedgroups == NULL) //如果接口没有白名单和黑名单(allowedgroups 为 NULL),则调用 insertRoute 函数将组地址插入路由表,并返回。
        {
            insertRoute(group, sourceVif->index, src);
            return;
        }

        // Check if this Request is legit on this interface
        bool                 allow_list = false;
        struct SubnetList   *match = NULL;
        struct SubnetList   *sn;

       //如果存在白名单,遍历白名单列表 allowedgroups,检查报告的组地址是否在白名单内。如果匹配成功,将 match 设置为匹配项。
        for(sn = sourceVif->allowedgroups; sn != NULL; sn = sn->next) {
            // Check if there is a whitelist
            if (sn->allow)
                allow_list = true;
            if((group & sn->subnet_mask) == sn->subnet_addr)
                match = sn;
        }

        if((!allow_list && match == NULL) ||
          (allow_list && match != NULL && match->allow)) { 
            //根据白名单的情况,判断是否将组地址插入路由表。如果是白名单,并且匹配项不为空且允许(match->allow 为真),或者没有白名单但匹配项为空,则插入路由表。
            // The membership report was OK... Insert it into the route table..
            insertRoute(group, sourceVif->index, src);
            return;
        }
        //如果不满足白名单条件,输出相应的日志信息。
        my_log(LOG_INFO, 0, "The group address %s may not be requested from this interface. Ignoring.", inetFmt(group, s1));
    } else {
        //如果接口不是下游接口,输出相应的日志信息。
        // Log the state of the interface the report was received on.
        my_log(LOG_INFO, 0, "Mebership report was received on %s. Ignoring.",
            sourceVif->state==IF_STATE_UPSTREAM?"the upstream interface":"a disabled interface");
    }
}
acceptLeaveMessage(src, group);

1、检查报文的有效性sourceVif->state == IF_STATE_DOWNSTREAM,表明下行接口自己需要离开组

2、检查是否是组里最后一个成员setRouteLastMemberMode(group, src);

void acceptLeaveMessage(uint32_t src, uint32_t group) {
    struct IfDesc   *sourceVif;

    my_log(LOG_DEBUG, 0,
        "Got leave message from %s to %s. Starting last member detection.",
        inetFmt(src, s1), inetFmt(group, s2));

    // Sanitycheck the group adress...
    if(!IN_MULTICAST( ntohl(group) )) {
        my_log(LOG_WARNING, 0, "The group address %s is not a valid Multicast group.",
            inetFmt(group, s1));
        return;
    }

    // Find the interface on which the report was received.
    sourceVif = getIfByAddress( src );
    if(sourceVif == NULL) {
        my_log(LOG_WARNING, 0, "No interfaces found for source %s",
            inetFmt(src,s1));
        return;
    }

    // We have a IF so check that it's an downstream IF.
    if(sourceVif->state == IF_STATE_DOWNSTREAM) {

        GroupVifDesc   *gvDesc;
        gvDesc = (GroupVifDesc*) malloc(sizeof(GroupVifDesc));

        // Tell the route table that we are checking for remaining members...
        setRouteLastMemberMode(group, src);

        // Call the group spesific membership querier...
        gvDesc->group = group;
        // gvDesc->vifAddr = sourceVif->InAdr.s_addr;
        gvDesc->started = 0;

        sendGroupSpecificMemberQuery(gvDesc);

    } else {
        // just ignore the leave request...
        my_log(LOG_DEBUG, 0, "The found if for %s was not downstream. Ignoring leave request.", inetFmt(src, s1));
    }
}
setRouteLastMemberMode

当启动快速离开机制fastUpstreamLeave =1 就调用sendJoinLeaveUpstream(croute, 0);发送一个离开的报文给上行接口。

void setRouteLastMemberMode(uint32_t group, uint32_t src) {
    struct Config       *conf = getCommonConfig();
    struct RouteTable   *croute;
    int                 routeStateCheck = 1;

    croute = findRoute(group);
    if(!croute)
        return;

    // Check for fast leave mode...
    if(conf->fastUpstreamLeave) {
        if(croute->upstrState == ROUTESTATE_JOINED) {
            // Remove downstream host from route
            clearDownstreamHost(conf, croute, src);
        }

        // Do route state check if there is no downstream host in hash table
        // This host does not have to been the last downstream host if hash collision occurred
        routeStateCheck = testNoDownstreamHost(conf, croute);

        if(croute->upstrState == ROUTESTATE_JOINED) {
            // Send a leave message right away but only when the route is not active anymore on any downstream host
            // It is possible that there are still some interfaces active but no downstream host in hash table due to hash collision
            if (routeStateCheck && numberOfInterfaces(croute) <= 1) {
                my_log(LOG_DEBUG, 0, "quickleave is enabled and this was the last downstream host, leaving group %s now", inetFmt(croute->group, s1));
                sendJoinLeaveUpstream(croute, 0);
            } else {
                my_log(LOG_DEBUG, 0, "quickleave is enabled but there are still some downstream hosts left, not leaving group %s", inetFmt(croute->group, s1));
            }
        }
    }

    // Set the routingstate to last member check if we have no known downstream host left or if fast leave mode is disabled...
    if(routeStateCheck) {
        croute->upstrState = ROUTESTATE_CHECK_LAST_MEMBER;

        // Set the count value for expiring... (-1 since first aging)
        croute->ageValue = conf->lastMemberQueryCount;
    }
}

sendGroupSpecificMemberQuery(gvDesc);

回调执行函数timer_setTimer周期性lastMemberQueryInterval查询工作,lastMemberGroupAge(gvDesc->group)用于根据前一次标记成员数来检测最后一个成员存不存在,具体成员检测在internAgeRoute()函数实现。

void sendGroupSpecificMemberQuery(void *argument) {
    struct  Config  *conf = getCommonConfig();
    struct  IfDesc  *Dp;
    int     Ix;

    // Cast argument to correct type...
    GroupVifDesc   *gvDesc = (GroupVifDesc*) argument;

    if(gvDesc->started) {
        // If aging returns false, we don't do any further action...
        if(!lastMemberGroupAge(gvDesc->group)) {
            // FIXME: Should we free gvDesc here?
            return;
        }
    } else {
        gvDesc->started = 1;
    }

    // Loop through all downstream interfaces
    for ( Ix = 0; (Dp = getIfByIx(Ix)); Ix++ ) {
        if ( Dp->InAdr.s_addr && ! (Dp->Flags & IFF_LOOPBACK) ) {
            if(Dp->state == IF_STATE_DOWNSTREAM) {
                // Is that interface used in the group?
                if (interfaceInRoute(gvDesc->group ,Dp->index)) {

                    // Send a group specific membership query...
                    sendIgmp(Dp->InAdr.s_addr, gvDesc->group,
                            IGMP_MEMBERSHIP_QUERY,
                            conf->lastMemberQueryInterval * IGMP_TIMER_SCALE,
                            gvDesc->group, 0, Dp->ifIndex);

                    my_log(LOG_DEBUG, 0, "Sent membership query from %s to %s. Delay: %d",
                            inetFmt(Dp->InAdr.s_addr,s1), inetFmt(gvDesc->group,s2),
                            conf->lastMemberQueryInterval);
                }
            }
        }
    }

    // Set timeout for next round...
    timer_setTimer(conf->lastMemberQueryInterval, sendGroupSpecificMemberQuery, gvDesc);
}

第六步:守护进程的清理:在程序退出前,会调用igmpProxyCleanUp函数进行清理。

igmpProxyCleanUp(); //进行清理操作。它关闭套接字、释放分配的资源。

void igmpProxyCleanUp(void) {
    my_log( LOG_DEBUG, 0, "clean handler called" );

    free_all_callouts();    // 清空定时器.
    clearAllRoutes();       // 删除内核mroute表,并在upstream发送离开消息
    disableMRouter();       // 关闭mroute的API接口
}

2、mroute 模块

是 Linux 内核中的一个组件,它支持多播路由(Multicast Routing)。

  1. 多播转发mroute 负责在 Linux 系统上转发多播数据包,确保数据包能到达所有订阅了特定多播组的主机。

  2. 多播路由表mroute 维护一个多播路由表(mroute table),记录了多播数据包的来源和目的地,以及它们在网络中的路径。

  3. IGMP 支持mroute 与 IGMP(Internet Group Management Protocol)协同工作,处理多播组成员的加入和离开。当主机加入或离开多播组时,mroute 会更新其路由表。

  4. PIM 支持:在一些配置中,mroute 可以与 PIM(Protocol Independent Multicast)协议配合使用,PIM 是一种用于多播路由的协议,可以跨越不同的网络层。

MRT_ADD_VIF   增加一个虚拟接口

MRT_DEL_VIF   删除一个虚拟接口

MRT_INIT   激活内核mrote功能

MRT_DONE   关闭内核mroute功能

MRT_ADD_MFC   增加一个组播转发项

MRT_DEL_MFC  删除一个组播转发项

  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值