最近关注了一些IP组播的知识,IP的组播需要以太网的支持。在这边文章内我们就主要讨论以太网如果支持IP组播。
首先看当前的interface是否支持multi-cast,如下面的命令红色部分标注,则说明当前的网卡支持组播,可以通过下面两个相关的设置选项进行设置。
allmulti
Enableor disable all-multicast mode. Ifselected, all multi‐
castpackets on the network will be received by the interface.
multicast
Set the multicast flag on the interface.This should not nor‐
mally beneeded as the drivers set the flag correctly them‐
selves.
[ansen@localhostwork]$ ifconfig
ens33: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet6fe80::250:56ff:fe33:bef prefixlen64 scopeid 0x20<link>
ether00:50:56:33:0b:ef txqueuelen 1000 (Ethernet)
RX packets37116 bytes 5195493 (4.9 MiB)
RX errors0 dropped 0 overruns 0 frame 0
TX packets183 bytes 17209 (16.8 KiB)
TX errors0 dropped 0 overruns 0 carrier 0 collisions 0
deviceinterrupt 19 base 0x2000
通过if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof (structip_mreq)) == -1)
使得当前网卡加入某个组。然后通过netstat–g 查看当前的网卡是否已经加入了哪些组。
默认如果当前网卡支持multi-cast,那么在网卡启动时已经默认加入224.0.0.1这个组。这个组代表当前网络上所有支持组播的主机。
[ansen@localhost work]$ netstat -ng
IPv6/IPv4 Group Memberships
Interface RefCntGroup
--------------- ------ ---------------------
lo 1 224.0.0.1
ens33 1 224.0.0.1
lo 1 ff02::1
lo 1 ff01::1
ens33 1 ff02::1:ff33:bef
ens33 1 ff02::1
ens33 1 ff01::1
在系统级别我们可以利用setsockopt使网卡加入某个组播组。那么在网卡驱动的层面是如何实现的呢。下面我们利用一份网卡驱动程序,详细解读以下在网卡驱动的实现。
下面两个地址代表了整个组播地址MAC地址的空间,对应到组播IP地址为224.0.0.0~239.255.255.255.
u_char ether_ipmulticast_min[6]= { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 };
u_char ether_ipmulticast_max[6]= { 0x01, 0x00, 0x5e, 0x7f, 0xff, 0xff };
#ifdef INET6
u_char ether_ipv6multicast_min[6]= { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 };
u_char ether_ipv6multicast_max[6]= { 0x33, 0x33, 0xff, 0xff, 0xff, 0xff };
#endif /* INET6 */
下面的宏实现了IP组播地址到MAC组播地址的转换。说的非常清楚。高25位固定,低23位是从IP组播地址拿过来的。
/*
* Macro to map an IPmulticast address to an Ethernet multicast address.
* The high-order 25bits of the Ethernet address are statically assigned,
* and the low-order23 bits are taken from the low end of the IP address.
*/
#define ETHER_MAP_IP_MULTICAST(ipaddr, enaddr) \
/*struct in_addr *ipaddr; */ \
/*u_int8_t enaddr[ETHER_ADDR_LEN]; */ \
{ \
(enaddr)[0]= 0x01; \
(enaddr)[1]= 0x00; \
(enaddr)[2]= 0x5e; \
(enaddr)[3]= ((u_int8_t *)ipaddr)[1] & 0x7f; \
(enaddr)[4]= ((u_int8_t *)ipaddr)[2]; \
(enaddr)[5]= ((u_int8_t *)ipaddr)[3]; \
下面的函数就是就是网卡驱动中设置加组播MAC地址的函数。这个函数会作为函数指针赋给if.ioctl这函数,供上层应用调用。
/*
* Add an Ethernetmulticast address or range of addresses to the list for a
* given interface.
*/
int
ether_addmulti(ifr, ac)
structifreq *ifr; //参数1是IP相关的参数
registerstruct arpcom *ac; //参数2大家从名字可以看出是ARP相关的参数
{
registerstruct ether_multi *enm;
structsockaddr_in *sin;
#ifdef INET6
structsockaddr_in6 *sin6;
#endif /* INET6 */
u_charaddrlo[6];
u_charaddrhi[6];
int s =splimp();
switch(ifr->ifr_addr.sa_family) {
caseAF_UNSPEC:
bcopy(ifr->ifr_addr.sa_data,addrlo, 6);
bcopy(addrlo,addrhi, 6);
break;
#ifdef INET
caseAF_INET: //查看参数中的IP地址族,如果是INET4则进行以下操作,我们关心的分支。
sin= (struct sockaddr_in *)&(ifr->ifr_addr);
if(sin->sin_addr.s_addr == INADDR_ANY) {
/* //这里设置网卡加入整个组播区间,就是用ifconfig设置almulticast
* An IP address of INADDR_ANY means listen toall
* of the Ethernet multicast addresses used forIP.
* (This is for the sake of IP multicast routers.)
*/
bcopy(ether_ipmulticast_min,addrlo, 6);
bcopy(ether_ipmulticast_max,addrhi, 6);
}
else{
//这里的两行的意思就是转化MAC地址,并设置组播区间,由于我们只有一个地址所以上下边界是同一个地址。
ETHER_MAP_IP_MULTICAST(&sin->sin_addr,addrlo);
bcopy(addrlo,addrhi, 6);
}
break;
#endif
#ifdef INET6
caseAF_INET6:
sin6= (struct sockaddr_in6 *)&(ifr->ifr_addr);
if(IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
/*
* An unspecified IPv6 address means listen toall
* of the IPv6 multicast addresses on thisEthernet.
* (Multicast routers like this.)
*/
bcopy(ether_ipv6multicast_min,addrlo, ETHER_ADDR_LEN);
bcopy(ether_ipv6multicast_max,addrhi, ETHER_ADDR_LEN);
}else {
ETHER_MAP_IN6_MULTICAST(sin6->sin6_addr,addrlo);
bcopy(addrlo,addrhi, ETHER_ADDR_LEN);
}
break;
#endif /* INET6 */
default:
splx(s);
return(EAFNOSUPPORT);
}
/*
* Verify that we have valid Ethernet multicastaddresses.
*/
//这里我们可以知道组播和单播MAC地址的区别就是低位第一个字节是否为1.
if((addrlo[0] & 0x01) != 1 || (addrhi[0] & 0x01) != 1) {
splx(s);
return(EINVAL);
}
/*
* See if the address range is already in thelist.
*/
ETHER_LOOKUP_MULTI(addrlo,addrhi, ac, enm);
if (enm!= NULL) {
/*
* Found it; just increment the referencecount.
*/
++enm->enm_refcount;
splx(s);
return(0);
}
/*
* New address or range; malloc a new multicastrecord
* and link it into the interface's multicastlist.
*/
最后这个组播地址或者区间会添加到当前网卡的组播列表。而这个列表会被网卡驱动设置到multi-castfilter 相关的寄存器。具体的函数请看最后。
enm =(struct ether_multi *)malloc(sizeof(*enm), M_IFMADDR, M_NOWAIT);
if (enm== NULL) {
splx(s);
return(ENOBUFS);
}
bcopy(addrlo,enm->enm_addrlo, 6);
bcopy(addrhi,enm->enm_addrhi, 6);
enm->enm_ac= ac;
enm->enm_refcount= 1;
LIST_INSERT_HEAD(&ac->ac_multiaddrs,enm, enm_list);
ac->ac_multicnt++;
splx(s);
/*
* Return ENETRESET to inform the driver thatthe list has changed
* and its reception filter should be adjustedaccordingly.
*/
return(ENETRESET);
}