原始程式檔列表
| lib.c | 公用函式,都是用在網路位址處理 |
| mcgroup.c | 處理 join/leace Multicast group |
| ifvc.c | 建立及提供搜尋 interface vector 功能 |
| config.c | 讀取 config 設定檔 |
| udpsock.c | 建立 udp sock |
| request.c | 處理 igmp request |
| igmp.c | 收/送 igmp 封包 |
| confread.c | config 子集,讀檔相關功能 |
| igmpproxy.c | igmpproxy 主流程 |
| syslog.c | 處理 log 訊息 |
| mroute-api.c | mroute api 相關程式 |
| kern.c | 不確定,看起來是打包用的 |
| rttable.c | igmpproxy 內部使用的 routing table 以及跟 kernel 增減 routing 的程式 |
| callout.c | 佇列處理 |
| igmpproxy.h | |
| os-linux.h | |
| os-openbsd.h | |
| os-freebsd.h | |
| os-dragonfly.h | |
| os-netbsd.h |
igmpproxy 運作概說
multicast 的訂閱是由 igmp 來進行,但是 multi 的特性類似於 broadcast,所以並不能透過 route 或 nat 轉發,所以需要在 router/gateway 上提供 igmp proxy 服務,在 downstream 收到 request 的時候,從 upstream 轉發出去,同時要去通知 kernel 收到這個 group 的 multicast 要轉發給 downstream。因為 downstream 有可能不只一個 host 要訂閱某個 group,igmpproxy 不可以在確定某 host leave 的時候就直接切斷這個 group 的 multicast routing,所以需要維護一套自己要看的 routing table,在確定 downstream 都沒有人有訂閱這個 group 的時候才能切斷。
後面的程式解析只看 igmpproxy 的主要流程,也就是收到 igmp 封包之後開始解析,看看該程式是如何做出 igmpproxy 所需的功能。
acceptIgmp()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // protocol == 0; kernel 發現新 source 的 group,所以要把他加入 routing 表 if (ip->ip_p == 0) { checkVIF = getIfByIx( upStreamVif ); // 根據 src 位址找出 upstream vif if(checkVIF == 0) { // 不是 upstream vif,跳開 else if(src == checkVIF->InAdr.s_addr) { // 根本就是自己發的,跳開 else if(!isAdressValidForIf(checkVIF, src)) { // 不在 upstream 的網段,跳開 } activateRoute(dst, src); // 啟用 routing,然後跳開 } // 後面會根據收到的 igmp 封包決定後續動作 switch (igmp->igmp_type) { case IGMP_V1_MEMBERSHIP_REPORT: case IGMP_V2_MEMBERSHIP_REPORT: // 收到 igmp membership report acceptGroupReport(src, group, igmp->igmp_type); return; case IGMP_V2_LEAVE_GROUP: // 收到 igmp leave acceptLeaveMessage(src, group); return; case IGMP_MEMBERSHIP_QUERY: // 收到 igmp query,總覺得應該要回應這個 query 比較好,可是實際操作沒問題,所以就當作不需要就好 return; default: // 其他當作不認識 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; } } |
acceptGroupReport()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if(!IN_MULTICAST( ntohl(group) )) {} // 如果 group 不在 multicast 位址範圍內,跳開 sourceVif = getIfByAddress( src ); // 根據 src 位址找出 vif if(sourceVif == NULL) {} // 找不到 vif,跳開 if(sourceVif->InAdr.s_addr == src) {} // src 就是自己,跳開 if(sourceVif->state == IF_STATE_DOWNSTREAM) { // 是 downstream 的 vif 才處理 if(sourceVif->allowedgroups == NULL) { // 沒有設定白名單 insertRoute(group, sourceVif->index); // 加入路由後跳開 } for(sn = sourceVif->allowedgroups; sn != NULL; sn = sn->next) { if((group & sn->subnet_mask) == sn->subnet_addr) { // 在白名單裡面 insertRoute(group, sourceVif->index); // 加入路由 } } } |
1 2 3 4 5 6 7 8 | if(!IN_MULTICAST( ntohl(group) )) {} // 如果 group 不在 multicast 位址範圍內,跳開 sourceVif = getIfByAddress( src ); // 根據 src 位址找出 vif if(sourceVif == NULL) {} // 找不到 vif,跳開 // 這次不檢查是不是自己發的!? if(sourceVif->state == IF_STATE_DOWNSTREAM) { // 是 downstream 的 vif 才處理 setRouteLastMemberMode(group); // 把 group 設定成沒有成員模式 sendGroupSpecificMemberQuery(gvDesc); // 送出 query,驗證是不是還有人要接收 group } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | croute = findRoute(group); // 根據 group 位址找 route 紀錄 if(croute==NULL) { // route 不存在 newroute = (struct RouteTable*)malloc(sizeof(struct RouteTable)); // 建立新的 route 紀錄 newroute->group = group; newroute->upstrState = ROUTESTATE_NOTJOINED; // 新的 route 所以設定為 notjoined BIT_ZERO(newroute->vifBits); // 清空要接收的 vif 表 croute = newroute; // croute 後面還會用到 if(ifx >= 0) { BIT_SET(newroute->vifBits, ifx); // 設定接收的 vif } if(routing_table == NULL) { // routing_table 還不存在,這個 newroute 就當頭 } else { // routing_table 已經存在,找適當位置插入這一筆新的 } } else { // route 已經存在 BIT_SET(croute->vifBits, ifx); // 更新要接收的 vif 表 if(croute->originAddr != 0) { // 有來源在播送給這個 group 的話 internUpdateKernelRoute(croute, 1) // 告訴 kernel 要把這個 group 轉送 } } if(croute->upstrState != ROUTESTATE_JOINED) { // 還沒對 upstream 送出 join sendJoinLeaveUpstream(croute, 1); // 送出 join report } |
1 2 3 4 5 6 7 | croute = findRoute(group); // 根據 group 尋找 route if(croute!=NULL) { // 有找到 if(croute->upstrState == ROUTESTATE_JOINED && conf->fastUpstreamLeave) { //已經送過 join 而且有設定 fast leave sendJoinLeaveUpstream(croute, 0); // 送出 leave report } croute->upstrState = ROUTESTATE_CHECK_LAST_MEMBER; // 更改 upstream 狀態 } |
sendGroupSpecificMemberQuery
1 2 3 4 5 6 7 8 9 | GroupVifDesc *gvDesc = (GroupVifDesc*) argument; // 把參數設定成 GroupVifDesc 型態 if(gvDesc->started) { // 已經啟用 if(!lastMemberGroupAge(gvDesc->group)) { // 最新一次的 member 檢查沒有得到 member 的回應 return; } else { gvDesc->started = 1; // 設定成啟動 } sendIgmp(...); // 送出 membership query,詢問還有誰在接收這個 group timer_setTimer(...); // 重設下次檢查時間 |
主程式流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main( int ArgCn, char *ArgVc[] ) { for (int c; (c = getopt(ArgCn, ArgVc, "vdh")) != -1;) { // 解析輸入參數 // d 將紀錄輸出到 stderr // v 紀錄 info 等級 // vv 紀錄 debug 等級 // h 顯示語法說明 } do { // 這個是主程式迴圈,還不是主迴圈,很有趣的寫法 :D,有什麼好處我不知道,請知道的人幫忙釋疑 if( ! loadConfig( configFilePath ) ) {} //讀取設定檔 if ( !igmpProxyInit() ) {} // 系統初始化 igmpProxyRun(); // 真正的主迴圈,開始收 igmp 封包跑 igmpproxy 流程 igmpProxyCleanUp(); // 清除 } while ( false ); } |
設定檔
1 2 3 4 5 6 7 8 9 | int loadConfig(char *configFile) { while ( token != NULL ) { if(strcmp("phyint", token)==0) { tmpPtr = parsePhyintToken(); // 解析 phy } else if(strcmp("quickleave", token)==0) {} // quickleave 標籤 ... ... } } |
PPP 連線的問題
先來看原本的程式 (ifvc.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // Get the interface adress... IfDescEp->InAdr = ((struct sockaddr_in *)&IfPt->ifr_addr)->sin_addr; addr = IfDescEp->InAdr.s_addr; memcpy( IfReq.ifr_name, IfDescEp->Name, sizeof( IfReq.ifr_name ) ); IfReq.ifr_addr.sa_family = AF_INET; ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr = addr; // Get the subnet mask... if (ioctl(Sock, SIOCGIFNETMASK, &IfReq ) < 0) my_log(LOG_ERR, errno, "ioctl SIOCGIFNETMASK for %s", IfReq.ifr_name); mask = ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr; subnet = addr & mask; /* get if flags ** ** typical flags: ** lo 0x0049 -> Running, Loopback, Up ** ethx 0x1043 -> Multicast, Running, Broadcast, Up ** ipppx 0x0091 -> NoArp, PointToPoint, Up ** grex 0x00C1 -> NoArp, Running, Up ** ipipx 0x00C1 -> NoArp, Running, Up */ if ( ioctl( Sock, SIOCGIFFLAGS, &IfReq ) < 0 ) my_log( LOG_ERR, errno, "ioctl SIOCGIFFLAGS" ); IfDescEp->Flags = IfReq.ifr_flags; // Insert the verified subnet as an allowed net... 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; |
因為 igmp proxy 是利用 interface 的 ip/netmask 來判斷封包是否過濾,一般的 IPoE 使用上都沒什麼問題,但是遇到 PPP 連線時,對方傳過來的 mcast 封包 source 會是 P-t-P dstaddr,而不是我們這邊的 IP。這個情形會導致 igmpproxy 把 mcast 都丟掉,所以要在 line 28 那邊作一些小改變。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // Get the interface adress... IfDescEp->InAdr = ((struct sockaddr_in *)&IfPt->ifr_addr)->sin_addr; addr = IfDescEp->InAdr.s_addr; memcpy( IfReq.ifr_name, IfDescEp->Name, sizeof( IfReq.ifr_name ) ); IfReq.ifr_addr.sa_family = AF_INET; ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr = addr; // Get the subnet mask... if (ioctl(Sock, SIOCGIFNETMASK, &IfReq ) < 0) my_log(LOG_ERR, errno, "ioctl SIOCGIFNETMASK for %s", IfReq.ifr_name); mask = ((struct sockaddr_in *)&IfReq.ifr_addr)->sin_addr.s_addr; subnet = addr & mask; /* get if flags ** ** typical flags: ** lo 0x0049 -> Running, Loopback, Up ** ethx 0x1043 -> Multicast, Running, Broadcast, Up ** ipppx 0x0091 -> NoArp, PointToPoint, Up ** grex 0x00C1 -> NoArp, Running, Up ** ipipx 0x00C1 -> NoArp, Running, Up */ if ( ioctl( Sock, SIOCGIFFLAGS, &IfReq ) < 0 ) my_log( LOG_ERR, errno, "ioctl SIOCGIFFLAGS" ); IfDescEp->Flags = IfReq.ifr_flags; // aimwang: when pppx get dstaddr for use if (0x10d1 == IfDescEp->Flags) { if ( ioctl( Sock, SIOCGIFDSTADDR, &IfReq ) < 0 ) my_log(LOG_ERR, errno, "ioctl SIOCGIFDSTADDR for %s", IfReq.ifr_name); addr = ((struct sockaddr_in *)&IfReq.ifr_dstaddr)->sin_addr.s_addr; subnet = addr & mask; } // Insert the verified subnet as an allowed net... 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; |
Line 29-36 是根據 IfDescEp->Flags == 0x10d1 時,addr 改取 dstaddr 而不是 srcaddr,然後重算 subnet,再交給後面加入 allowednets。這樣就可以讓 PPPoE 也能享受 igmp proxy,提供用戶極致的影音服務。