//go:build linux package multicast import ( "fmt" "net" "os" "syscall" "time" ) const ( // same size as GStreamer's rtspsrc multicastTTL = 16 ) // https://cs.opensource.google/go/x/net/+/refs/tags/v0.15.0:ipv4/sys_asmreq.go;l=51 func setIPMreqInterface(mreq *syscall.IPMreq, ifi *net.Interface) error { if ifi == nil { return nil } ifat, err := ifi.Addrs() if err != nil { return err } for _, ifa := range ifat { switch ifa := ifa.(type) { case *net.IPAddr: if ip := ifa.IP.To4(); ip != nil { copy(mreq.Interface[:], ip) return nil } case *net.IPNet: if ip := ifa.IP.To4(); ip != nil { copy(mreq.Interface[:], ip) return nil } } } return fmt.Errorf("no such interface") } type rawConn struct { fd uintptr } // Control implements syscall.RawConn. func (c *rawConn) Control(f func(fd uintptr)) error { f(c.fd) return nil } // Read implements syscall.RawConn. func (*rawConn) Read(_ func(fd uintptr) (done bool)) error { panic("unimplemented") } // Write implements syscall.RawConn. func (*rawConn) Write(_ func(fd uintptr) (done bool)) error { panic("unimplemented") } // singleConn is a multicast connection // that works on a single interface. type singleConn struct { addr *net.UDPAddr file *os.File conn net.PacketConn } // NewSingleConn allocates a singleConn. func NewSingleConn( intf *net.Interface, address string, _ func(network, address string) (net.PacketConn, error), ) (Conn, error) { addr, err := net.ResolveUDPAddr("udp4", address) if err != nil { return nil, err } sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) if err != nil { return nil, err } err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } err = syscall.SetsockoptString(sock, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, intf.Name) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } var lsa syscall.SockaddrInet4 lsa.Port = addr.Port copy(lsa.Addr[:], addr.IP.To4()) err = syscall.Bind(sock, &lsa) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } var mreq syscall.IPMreq copy(mreq.Multiaddr[:], addr.IP.To4()) err = setIPMreqInterface(&mreq, intf) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } err = syscall.SetsockoptIPMreq(sock, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, &mreq) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } var mreqn syscall.IPMreqn mreqn.Ifindex = int32(intf.Index) err = syscall.SetsockoptIPMreqn(sock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, &mreqn) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } err = syscall.SetsockoptInt(sock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL, multicastTTL) if err != nil { syscall.Close(sock) //nolint:errcheck return nil, err } file := os.NewFile(uintptr(sock), "") conn, err := net.FilePacketConn(file) if err != nil { file.Close() return nil, err } return &singleConn{ addr: addr, file: file, conn: conn, }, nil } // Close implements Conn. func (c *singleConn) Close() error { c.conn.Close() c.file.Close() return nil } // SetReadBuffer implements Conn. func (c *singleConn) SetReadBuffer(bytes int) error { return syscall.SetsockoptInt(int(c.file.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes) } // SyscallConn implements Conn. func (c *singleConn) SyscallConn() (syscall.RawConn, error) { return &rawConn{fd: c.file.Fd()}, nil } // LocalAddr implements Conn. func (c *singleConn) LocalAddr() net.Addr { return c.conn.LocalAddr() } // SetDeadline implements Conn. func (c *singleConn) SetDeadline(_ time.Time) error { panic("unimplemented") } // SetReadDeadline implements Conn. func (c *singleConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } // SetWriteDeadline implements Conn. func (c *singleConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } // WriteTo implements Conn. func (c *singleConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.conn.WriteTo(b, addr) } // ReadFrom implements Conn. func (c *singleConn) ReadFrom(b []byte) (int, net.Addr, error) { return c.conn.ReadFrom(b) }