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)。
多播转发:
mroute
负责在 Linux 系统上转发多播数据包,确保数据包能到达所有订阅了特定多播组的主机。多播路由表:
mroute
维护一个多播路由表(mroute table),记录了多播数据包的来源和目的地,以及它们在网络中的路径。IGMP 支持:
mroute
与 IGMP(Internet Group Management Protocol)协同工作,处理多播组成员的加入和离开。当主机加入或离开多播组时,mroute
会更新其路由表。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 删除一个组播转发项