Search code examples
pythongonetwork-programmingmulticast

How can I translate this TCP/IP MULTICAST code from Python to Go


I have this code, written in Python. From what I understand it

  • Creates a single socket
  • Adds every local IP address as a member of the given multicast groups.

If any local interface receives a multicast message with that group/port, recv will return the result.

import sys
import socket
import struct

MULTICASTGROUP = "230.0.0.2"
MULTICASTPORT = 4450

hostname = socket.gethostname()
hosts = socket.gethostbyname_ex(hostname)
hostinterfaceips = hosts[2]

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', MULTICASTPORT))
      
for inf in hostinterfaceips:
    sock.setsockopt(
       socket.IPPROTO_IP,
       socket.IP_ADD_MEMBERSHIP,
       socket.inet_aton(MULTICASTGROUP) + socket.inet_aton(inf)
    )

data = sock.recv(1500)
print(data)

How can I adapt this code to Go? This is as far as I've gotten. I have a really hard time understanding the net package, especially regarding Multicast

func TestHost(t *testing.T) {
    hostname, err := os.Hostname()
    if err != nil {
        t.Fatal(err)
    }
    fmt.Printf("hostname: %s\n", hostname)
    addrs, err := net.LookupHost(hostname)
    if err != nil {
        t.Fatal(err)
    }
    for i, addr := range addrs {
        fmt.Printf("%d: %s\n", i, addr)
    }
}

Solution

  • As it turned out, my issues stemmed from the fact that the net package disables multicast on loopback by default. You have to turn it back on using something like the below code:

    func createMulticastConnection(group string, port int) (*net.UDPConn, error) {
        interfaces, err := getActiveInterfaces()
        if err != nil {
            return nil, err
        }
        var iface *net.Interface
    
        // This grabs the first physical interface with multicast that is up.
        for i := range interfaces {
            if interfaces[i].Flags&net.FlagMulticast != 0 {
                iface = interfaces[i]
                break
            }
        }
        if iface == nil {
            return nil, fmt.Errorf("no valid interface found")
        }
        addr := net.UDPAddr{
            IP:   net.ParseIP(group),
            Port: port,
        }
        conn, err := net.ListenUDP("udp", &addr)
        if err != nil {
            return nil, err
        }
    
        // This code enables loopback for multicast messages. By default,
        // net disables loopback multicast and this is the only way
        // to turn it back on.
        if addr.IP.To4() != nil {
            pc := ipv4.NewPacketConn(conn)
            if err := pc.JoinGroup(iface, &addr); err != nil {
                return nil, err
            }
            if err := pc.SetMulticastLoopback(true); err != nil {
                return nil, err
            }
        } else if addr.IP.To16() != nil {
            pc := ipv6.NewPacketConn(conn)
            if err := pc.JoinGroup(iface, &addr); err != nil {
                return nil, err
            }
            if err := pc.SetMulticastLoopback(true); err != nil {
                return nil, err
            }
        }
    
        return conn, nil
    }
    // Returns a slice of "valid" addresses, i.e. ones
    // that are "up" (whatever that means) and have an
    // actual hardware address (meaning they aren't VPN interfaces)
    func getActiveInterfaces() ([]*net.Interface, error) {
        interfaces, err := net.Interfaces()
        if err != nil {
            return nil, err
        }
        ret := []*net.Interface{}
        for i := range interfaces {
            if interfaces[i].Flags&net.FlagUp != 0 &&
                interfaces[i].HardwareAddr != nil {
                ret = append(ret, &interfaces[i])
            }
        }
        return ret, nil
    }