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
}
Modifying the application to listen on one of the following IP addresses will make it work on Linux and macOS:
rx_multicastAddr
in the question)0.0.0.0
).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])
}
}
}