环境
window10 gns3
gns3安装
放一个别人的链接,blog里面写的很详细,应该怎么安装gns3环境。也有打包的资源。
不过不需要完全安装。百度云下载速度我只想吐槽。所以我们只需要下载gns3和一个cisco路由器的镜像,比如c2691,使用本地作为gns的server。
网络拓扑
cloud 是用来连接本地的虚拟环回口,可以查看网络管理,网络适配器是否存在loop口。没有的话,百度一下!
我添加了两个环回口,并配置了ip地址。
路由器配置
enable
//进入配置模式
config t
//开启组播
ip multicast-routing
//配置结构f0/0
interface FastEthernet 0/0
ip address 192.168.10.1 255.255.255.0
//使用igmp dm 模式
ip pim dense-mode
interface FastEthernet 0/1
ip address 192.168.20.1 255.255.255.0
ip pim dense-mode
到这一步组播网络就已经弄好了。
模拟组播
我们需要一个组播源和一个加入组播的成员。
在网上找了一下,发现了VLC media player 可以作为组播源,推送rtp流。使用一个VLC推流,本地一个VLC接收流能够播放MP4,但是wireshark没有抓到包。不知道从那个网卡出去了。看网上说通过配置路由表可以指定出口网卡。然而并没有用。如果有知道的朋友麻烦留言告知一下。
route -p add 224.1.1.4 mask 255.0.0.0 192.168.10.1 metric 3
使用c++程序模拟组播源和接受者
需要改一哈本机地址编译。
组播源
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
static const auto LOCAL_IP = "192.168.10.2";
static const auto MULTICAST_GROUP_ADDRESS = "239.255.43.21";
static const unsigned short MULTICAST_GROUP_PORT = 45454;
static const int MSGBUFSIZE = 1024;
int init_winsock()
{
int iResult;
WSADATA wsaData;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
return 0;
}
int test_mcast_send()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd) {
printf("socket error!!!\n");
perror("socket:");
return -1;
}
int ttl = 60; /* max = 255 */
//int aaret = setsockopt(sockfd, IPPROTO_IP, IP_TTL, (char*)&ttl, sizeof(ttl));
int aaret = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)); //设置组播TT
if (-1 == aaret) {
printf("IP_TTL error!!!\n");
perror("setsockopt:");
closesocket(sockfd);
return -1;
}
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
perror("Setting SO_REUSEADDR error");
closesocket(sockfd);
return -1;
}
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(MULTICAST_GROUP_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
int addr_len = sizeof(addr);
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
if (-1 == ret) {
printf("bind localaddr error!!!\n");
perror("bind:");
closesocket(sockfd);
return -1;
}
unsigned long if_addr = inet_addr(LOCAL_IP);
ret = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&if_addr, sizeof(if_addr));
if (-1 == ret) {
printf("IP_MULTICAST_IF error!!!\n");
perror("setsockopt:");
closesocket(sockfd);
return -1;
}
char host[1024] = { 0 };
gethostname(host, 1024);
int msgNo = 0;
char msg[1024] = { 0 };
addr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP_ADDRESS);
while (true) {
//sprintf(msg, "Groupcast Message %s No.%d", host, msgNo++);
sprintf(msg, "ControlCenterMessage %d", msgNo++);
int ret = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&addr, addr_len);
if (ret < 0) {
perror("sendto");
return -1;
}
else {
printf("Sent msg: %s\n", msg);
}
Sleep(1000);
}
return 0;
}
int main()
{
int ret = init_winsock();
if (0 != ret) { return ret; }
ret = test_mcast_send();
getchar();
}
接收者
//#include <Windows.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
static const auto LOCAL_IP = "192.168.20.5";
static const auto MULTICAST_GROUP_ADDRESS = "239.255.43.21";
static const unsigned short MULTICAST_GROUP_PORT = 45454;
static const int MSGBUFSIZE = 1024;
int init_winsock()
{
int iResult;
WSADATA wsaData;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
return 0;
}
int test_multicast_with_local_ip()
{
int ret = 0;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd) {
printf("socket error!!!\n");
perror("socket:");
return -1;
}
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
perror("Setting SO_REUSEADDR error");
closesocket(sockfd);
return -1;
}
/*test ip*/
struct sockaddr_in localaddr = { 0 };
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(MULTICAST_GROUP_PORT);
localaddr.sin_addr.s_addr = /*inet_addr(LOCAL_IP)*/ htonl(INADDR_ANY);
ret = bind(sockfd, (struct sockaddr*)&localaddr, sizeof(struct sockaddr));
if (-1 == ret) {
printf("bind localaddr error!!!\n");
perror("bind:");
closesocket(sockfd);
return -1;
}
/*设置是否支持本地回环接收*/
/*int loopBack = 1;
ret = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&loopBack, sizeof(loopBack));
if (-1 == ret) {
printf("setsockopt broadcaset error!!!\n");
perror("setsockopt:");
closesocket(sockfd);
return -1;
}*/
/*
将本地socket添加到多播组中,注意,此处针对struct ip_mreq结构体需要填充两个成员,
成员ipmr.imr_interface.s_addr的值指定的是将要发送的网卡的ip地址,
成员impr.imr_multiaddr指定的是组播地址;
如果指定为INADDR_ANY则系统会绑定一个默认网卡的具体ip(根据默认网关选择),则会出现特定网卡可以发送和接收组播信息,另一网卡不可以。
即指定INADDR_ANY并不能把所有网卡都添加多播组中,必须明确指定对应网卡ip才可以。*/
//struct in_addr addr = { 0 };
//addr.s_addr = inet_addr(local_ip);
struct ip_mreq ipmr = { 0 };
ipmr.imr_interface.s_addr = inet_addr(LOCAL_IP) /*(INADDR_ANY)*/;
ipmr.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP_ADDRESS);
int len = sizeof(ipmr);
ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&ipmr, len);
if (-1 == ret) {
printf("set error IP_ADD_MEMBERSHIP %d\n", WSAGetLastError());
perror("setsockopt:");
closesocket(sockfd);
return -1;
}
/*此处指定组播数据的出口网卡,如果不设置则会根据路由表指定默认路由出口*/
/*if (-1 == setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&addr, sizeof(addr))) {
printf("set error IP_MULTICAST_IF %s\n", local_ip);
perror("Setting IP_MULTICAST_IF error:");
closesocket(sockfd);
sockfd = -1;
}*/
/*struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
if (-1 == ret) {
printf("setsockopt recvtimeout error!!!\n");
perror("setsockopt:");
closesocket(sockfd);
return -1;
}*/
/* now just enter a read-print loop */
char msgbuf[MSGBUFSIZE];
int nbytes = 0;
//localaddr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP_ADDRESS);
localaddr.sin_addr.s_addr = inet_addr(LOCAL_IP);
while (1) {
int addrlen = sizeof(localaddr);
if ((nbytes = recvfrom(sockfd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &localaddr, &addrlen)) < 0) {
perror("recvfrom");
return -1;
}
msgbuf[nbytes] = 0;
puts(msgbuf);
}
return 0;
}
int test_multicast()
{
int fd;
/* create what looks like an ordinary UDP socket */
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket");
return(1);
}
/**** MODIFICATION TO ORIGINAL */
/* allow multiple sockets to use the same PORT number */
u_int yes = 1; /*** MODIFICATION TO ORIGINAL */
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes)) < 0) {
perror("Reusing ADDR failed");
return(1);
}
/*** END OF MODIFICATION TO ORIGINAL */
/* set up destination address */
struct sockaddr_in addr = {};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* N.B.: differs from sender */
addr.sin_port = htons(MULTICAST_GROUP_PORT);
/* bind to receive address */
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("bind");
return(1);
}
/* use setsockopt() to request that the kernel join a multicast group */
struct ip_mreq mreq = {};
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP_ADDRESS);
//mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) < 0) {
perror("IP_ADD_MEMBERSHIP");
//return(1);
}
/* now just enter a read-print loop */
int nbytes = 0;
int addrlen = sizeof(addr);
char msgbuf[MSGBUFSIZE] = {};
while (1) {
if ((nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen)) < 0) {
perror("recvfrom");
return(1);
}
msgbuf[nbytes] = 0;
printf("receive:%s\n", msgbuf);
}
return 0;
}
int main()
{
int ret = init_winsock();
if (0 != ret) { return ret; }
ret = test_multicast_with_local_ip();
//ret = test_multicast();
getchar();
}
实验结果查看
本来接收者可以直接打印出消息的。后面抓包发现,组播流被封装在了负载里面。所有只有抓包才能查看结果了。
遇到的问题
1:添加了环回口后,连接cloud和路由器报错:网卡打开失败.
解决:电脑没有安装winpcap。 或者以前安装过,下载不干净,导致安装失败。卸载干净后重新安装。
2:组播包到达路由器后没有被转发。
解决:网上搜到一篇写的很详细的排查过程。特别厉害。
igmp错误排查
我的问题是构建的组播报文ttl值为1.导致路由器直接将包丢弃了。修改组播报文ip.tll为60.