Files
gortsplib/pkg/multicast/single_conn_lin.go
2025-08-11 10:49:54 +02:00

198 lines
4.3 KiB
Go

//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)
}