问题现象:network设置成host模式,创建两个docker:test1和test2,docker上运行测试程序udprecv.out -r 233.13.13.13 13131 和udprecv.out -r 233.23.23.23 13131 。意图分别接收233.13.13.13:13131和233.23.23.23:13131(同端口,不同组播地址)。结果两个docker都到了两个组播的数据。

RECV#IP: 172.16.1.135 Port: 37554 Bytes: 24Tue Mar 22 04:33:47 2022
hehehe233.23.23.23:13131
RECV#IP: 172.16.1.135 Port: 57490 Bytes: 24Tue Mar 22 04:33:47 2022
hahaha233.13.13.13:13131
RECV#IP: 172.16.1.135 Port: 37554 Bytes: 24Tue Mar 22 04:33:48 2022
hehehe233.23.23.23:13131
RECV#IP: 172.16.1.135 Port: 57490 Bytes: 24Tue Mar 22 04:33:48 2022
hahaha233.13.13.13:13131
 

问题环境:centos7的最小安装包版docker,不论使用什么语言(本次遇到问题的项目是go),复现和解决问题使用的c++。

问题解决:UDP接收socket还是需要bind端口的,一般情况下,在setsockopt加入组播之前,为了防止让组播udp变成单播udp,bind的地址一般是INADDR_ANY。

    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        std::cout<<"Socket initialization failed"<<std::endl;
        return;
    }

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(tgtPort);
        
    bind(sockfd, (struct sockaddr*)&address, sizeof(address));

因为如果bind使用本地ip(比如192.168.1.133之类的eth0的地址),这个socket就变单播了。后面的加入组播的代码也就没有意义了。

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(tgtIP);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq));

问题是在--network host模式下,如果bind的时候设置了INADDR_ANY,感觉回到宿主机的网络上的时候就只对端口生效了,docker里不同组播地址就分不开了。所以,给docker启动的时候更换模式可能也是一个解决方案(简单使用原生的bridge模式试过了,不行的,应该用自定义的bridge加上设置应该也能解决,我没试),在程序里bind的时候不用INADDR_ANY换成打算接收的目标组播IP,应该也算是一个解决方案。

    //0、设置端口复用(看实际需要)
    int reuse = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    //1、bind 打算接收的目标端口和目标ip
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    // address.sin_addr.s_addr = htonl(INADDR_ANY); 
    /* host模式的docker上应该绑定 233.13.13.13 上的13131。而不是0.0.0.0上的13131 */
    address.sin_addr.s_addr = inet_addr(tgtIP);
    address.sin_port = htons(tgtPort);
    bind(sockfd, (struct sockaddr*)&address, sizeof(address));
    
    //2、加入组播,貌似组播的interface设置成0.0.0.0反而没关系
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(tgtIP);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq));

封面的图是网上找的一个讲解docker网络模式的图,遇到类似问题的可以回顾一下自己docker的网络模式设置是不是弄成了host。如果和宿主机设置好自定义的bridge模式貌似也能解决这个问题。哪个高手对这个docker下的组播机理比较了解的,或者设置bridge模式成功的,希望能指导一下,先行谢过了。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐