如何在 Golang 中的组播 UDPConn 上设置 IP_MULTICAST_LOOP

How to set IP_MULTICAST_LOOP on multicast UDPConn in Golang

我需要在多播 UDP connection/socket 上设置 IP_MULTICAST_LOOP 以便我可以 send/receive 在我的本地机器上多播数据包。这是我发现应该有效的调用:

l, err := net.ListenMulticastUDP("udp4", nil, addr)
file, err := l.File()
fd := syscall.Handle(file.Fd())

err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, 1)

但是它失败了"not supported by windows"。我很确定 windows 中的套接字支持它,只是不在 Go net 包中。关于如何在我的连接上设置此标志的任何想法? (我是 Go 语言的新手,我可能忽略了一些明显的东西)。这是在 Windows 上进行的,我还没有机会在 Linux 上进行测试。

作为一般用途的 doc says, net.ListenMulticastUDP is just for convenience of simple small applications. You can use golang.org/x/net/ipv4,这个包为您提供了更多关于多播的选项。实际上,net.ListenMulticastUDP() 的源代码将 IP_MULTICAST_LOOP 设置为 false:

func listenIPv4MulticastUDP(c *UDPConn, ifi *Interface, ip IP) error {
    if ifi != nil {
        if err := setIPv4MulticastInterface(c.fd, ifi); err != nil {
            return err 
        }   
    }   
    if err := setIPv4MulticastLoopback(c.fd, false); err != nil {
        return err 
    }   
    if err := joinIPv4Group(c.fd, ifi, ip); err != nil {
        return err 
    }   
    return nil 
}

setIPv4MulticastLoopback() 是为不同的操作系统实现的,它没有被导出。对于 Windows,它位于 sockoptip_windows.go:

func setIPv4MulticastLoopback(fd *netFD, v bool) error {
    if err := fd.incref(); err != nil {
        return err 
    }   
    defer fd.decref()
    return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, boolint(v)))
}

下面是golang.org/x/net/ipv4的例子,你可以get/setMulticastLoopback

package main

import (
    "fmt"
    "net"
    "golang.org/x/net/ipv4"
)

func main() {
    ipv4Addr := &net.UDPAddr{IP: net.IPv4(224, 0, 0, 251), Port: 5352}
    conn, err := net.ListenUDP("udp4", ipv4Addr)
    if err != nil {
        fmt.Printf("ListenUDP error %v\n", err)
        return
    }

    pc := ipv4.NewPacketConn(conn)

    // assume your have a interface named wlan
    iface, err := net.InterfaceByName("wlan")
    if err != nil {
        fmt.Printf("can't find specified interface %v\n", err)
        return
    }
    if err := pc.JoinGroup(iface, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 251)}); err != nil {
        return
    }

    // test
    if loop, err := pc.MulticastLoopback(); err == nil {
        fmt.Printf("MulticastLoopback status:%v\n", loop)
        if !loop {
            if err := pc.SetMulticastLoopback(true); err != nil {
            fmt.Printf("SetMulticastLoopback error:%v\n", err)
            }
        }
    }

    if _, err := conn.WriteTo([]byte("hello"), ipv4Addr); err != nil {
        fmt.Printf("Write failed, %v\n", err)
    }


    buf := make([]byte, 1024)
    for {
        if n, addr, err := conn.ReadFrom(buf); err != nil {
            fmt.Printf("error %v", err)
        } else {
            fmt.Printf("recv %s from %v\n", string(buf[:n]), addr)
        }
    }

    return
}

我认为 stdlib ListenMulticastUDP 默认禁用环回有点愚蠢,因为它使它完全无法用于测试目的(即 运行 测试机器上的多个节点)并且它没有说在文档中清楚地表明它专门禁用环回,这是不合理且令人讨厌的。

https://github.com/p9c/pod/blob/master/pkg/comm/multicast/channel.go 是一个简单的函数,returns 一个标准的 224.0.0.1 具有任意端口并启用了环回。我想如果它让你选择环回地址会更好,但对于构​​建 LAN 多播 IPv4 系统,这就是你所需要的。