gomulticast

Multicast doesn't work on Linux in golang


This code sends and receives multicast paquets.

This code works on Windows 10 but not with Linux : why ?

Packed are sent (IP 230.0.0.1, dest port 9001) but application doesn't receive multicast

packet (IP 230.0.0.2, dest port 9002).

What is the problem ?

To test my application, I use a Linux VM : perahaps, it is the cause ?

package main

import (
    "net"
    "os"
    "strconv"
    "time"

    "github.com/rs/zerolog"
    "golang.org/x/net/ipv4"
)

const device1_tx_multicastAddr = "230.0.0.1"
const device1_tx_udp_port = 9001
const device2_tx_multicastAddr = "230.0.0.2"
const device2_tx_udp_port = 9002
const packetTxDelayMs = 1000

// const ethName = "Ethernet" // Windows
const ethName = "eth0" // Linux

const modeDevice2 = false // Device 1
//const modeDevice2 = true // Device 2

var logConsole zerolog.Logger

func main() {
    logConsole = zerolog.New(os.Stderr).With().Timestamp().
        Str("module", "main").
        Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr}).
        Level(zerolog.InfoLevel)

    // **********************************
    // Initialize Tx
    localInterface := getInterfaceByName(ethName)
    logConsole.Info().Str("func", "main").Msg("localInterface: " + ethName)

    tx_multicastAddr := device1_tx_multicastAddr
    rx_multicastAddr := device2_tx_multicastAddr
    tx_udp_port := device1_tx_udp_port
    rx_udp_port := device2_tx_udp_port

    if modeDevice2 {
        tx_multicastAddr = device2_tx_multicastAddr
        rx_multicastAddr = device1_tx_multicastAddr
        tx_udp_port = device2_tx_udp_port
        rx_udp_port = device1_tx_udp_port
    }

    logConsole.Info().Str("func", "main").Msg("Open Tx UDP port " + tx_multicastAddr + ":" + strconv.Itoa(tx_udp_port) + "...")
    remoteDeviceUdpAddr, err := net.ResolveUDPAddr("udp4", tx_multicastAddr+":"+strconv.Itoa(tx_udp_port))
    if err != nil {
        panic(err)
    }

    localDeviceUdpAddr, err2 := net.ResolveUDPAddr("udp4", localInterface.String()+":"+strconv.Itoa(rx_udp_port))
    if err2 != nil {
        panic(err2)
    }

    logConsole.Info().Str("func", "main").Msg("Listen UDP: " + localDeviceUdpAddr.String() + "...")
    localDevice, err2 := net.ListenUDP("udp4", localDeviceUdpAddr)
    if err2 != nil {
        panic(err2)
    }

    // **********************************
    // Initialize Rx
    udpReceiver := ipv4.NewPacketConn(localDevice)
    ief, errInterface := net.InterfaceByName(ethName)
    if errInterface != nil {
        localDevice.Close()
        panic(errInterface)
    }
    logConsole.Info().Str("func", "main").Msg("Join Multicast: " + rx_multicastAddr + "...")
    err = udpReceiver.JoinGroup(ief, &net.UDPAddr{IP: net.ParseIP(rx_multicastAddr)})

    if err != nil {
        localDevice.Close()
        panic(err)
    }

    // **********************************
    // Run Rx/Tx tasks
    go sendData(localDevice, remoteDeviceUdpAddr, packetTxDelayMs)
    receivedData(udpReceiver)
}

// *************************************************
func sendData(localDevice *net.UDPConn, remoteDeviceUdpAddr *net.UDPAddr, packetDelay uint) {
    data := []byte("1234567890")

    for {
        //logConsole.Info().Str("func", "sendData").Msg("Send...")
        _, err := localDevice.WriteTo(data, remoteDeviceUdpAddr)
        if err != nil {
            panic(err)
        }
        time.Sleep(time.Duration(packetDelay) * time.Millisecond)
    }
}
func receivedData(receiver *ipv4.PacketConn) {
    buf := make([]byte, 1500)
    for {
        n, _, _, err := receiver.ReadFrom(buf)
        if err == nil {
            logConsole.Info().Str("func", "receivedData").Msg("Receive Data: " + string(buf[0:n]))
        }
    }
}

// *************************************************
func getInterfaceByName(name string) net.IP {
    ief, err := net.InterfaceByName(name)
    if err != nil {
        panic(err)
    }
    addrs, err := ief.Addrs()
    if err != nil {
        panic(err)
    }

    var ipAddr net.IP
    for _, addr := range addrs {
        ipAddr = addr.(*net.IPNet).IP.To4()
        if ipAddr != nil {
            break
        }
    }
    if ipAddr == nil {
        panic("ipAddr is nil")
    }
    return ipAddr
}

Solution

  • Modifying the application to listen on one of the following IP addresses will make it work on Linux and macOS:

    But it's not clear should it work when it listens on an IP address of a NIC (for example, 192.168.0.5). According to my test and the description in the question, it works on Windows but does not work on Linux or macOS. I can not find an authoritative source that describes this behavior yet.


    Below is a simplified demo that accepts flags.

    On device 1, run it with this command (replace the interface name with the one for your device):

    go run . -listen 230.0.0.1:9001 -join 230.0.0.1:9001 -send 230.0.0.2:9002 -ifname eth0
    

    On device 2, run it with this command (replace the interface name with the one for your device):

    go run . -listen 0.0.0.0:9002 -join 230.0.0.2:9002 -send 230.0.0.1:9001 -ifname Ethernet
    
    package main
    
    import (
        "flag"
        "log"
        "net"
        "time"
    
        "golang.org/x/net/ipv4"
    )
    
    var (
        listen string
        join   string
        send   string
        ifname string
    )
    
    func main() {
        flag.StringVar(&listen, "listen", "230.0.0.1:9001", "")
        flag.StringVar(&join, "join", "230.0.0.1:9001", "the multicast group address to receive data from")
        flag.StringVar(&send, "send", "230.0.0.2:9002", "the multicast group address to send data to")
        flag.StringVar(&ifname, "ifname", "eth0", "the name of the interface")
        flag.Parse()
    
        itf, err := net.InterfaceByName(ifname)
        if err != nil {
            panic(err)
        }
    
        groupAddr, err := net.ResolveUDPAddr("udp4", join)
        if err != nil {
            panic(err)
        }
    
        c, err := net.ListenPacket("udp4", listen)
        if err != nil {
            panic(err)
        }
        defer c.Close()
    
        p := ipv4.NewPacketConn(c)
        if err := p.JoinGroup(itf, &net.UDPAddr{IP: groupAddr.IP}); err != nil {
            panic(err)
        }
        log.Printf("join multicast group %s, waiting...", join)
    
        go sendData(c, send)
        receivedData(p)
    }
    
    func sendData(c net.PacketConn, target string) {
        data := []byte(ifname)
    
        addr, err := net.ResolveUDPAddr("udp4", target)
        if err != nil {
            panic(err)
        }
    
        for {
            _, err := c.WriteTo(data, addr)
            if err != nil {
                panic(err)
            }
            time.Sleep(time.Second)
        }
    }
    
    func receivedData(receiver *ipv4.PacketConn) {
        buf := make([]byte, 1500)
        for {
            n, _, _, err := receiver.ReadFrom(buf)
            if err == nil {
                log.Printf("Receive Data from: %s\n", buf[0:n])
            }
        }
    }