diff --git a/internal/webrtc/webrtc.go b/internal/webrtc/webrtc.go index 960bc689..2a5b4ad6 100644 --- a/internal/webrtc/webrtc.go +++ b/internal/webrtc/webrtc.go @@ -57,6 +57,15 @@ func Init() { } } + webrtc.OnNewListener = func(ln any) { + switch ln := ln.(type) { + case *net.TCPListener: + log.Info().Stringer("addr", ln.Addr()).Msg("[webrtc] listen tcp") + case *net.UDPConn: + log.Info().Stringer("addr", ln.LocalAddr()).Msg("[webrtc] listen udp") + } + } + var err error // create pionAPI with custom codecs list and custom network settings @@ -70,7 +79,6 @@ func Init() { clientAPI = serverAPI if address != "" { - log.Info().Str("addr", cfg.Mod.Listen).Msg("[webrtc] listen") clientAPI, _ = webrtc.NewAPI() } diff --git a/pkg/webrtc/api.go b/pkg/webrtc/api.go index 79cf6d3c..5551d65e 100644 --- a/pkg/webrtc/api.go +++ b/pkg/webrtc/api.go @@ -1,7 +1,9 @@ package webrtc import ( + "fmt" "net" + "slices" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/xnet" @@ -27,6 +29,69 @@ type Filters struct { UDPPorts []uint16 `yaml:"udp_ports"` } +func (f *Filters) Network(protocol string) string { + if f == nil || f.Networks == nil { + return protocol + } + v4 := slices.Contains(f.Networks, protocol+"4") + v6 := slices.Contains(f.Networks, protocol+"6") + if v4 && v6 { + return protocol + } else if v4 { + return protocol + "4" + } else if v6 { + return protocol + "6" + } + return "" +} + +func (f *Filters) NetIPs() (ips []net.IP) { + itfs, _ := net.Interfaces() + for _, itf := range itfs { + if itf.Flags&net.FlagUp == 0 { + continue + } + if !f.IncludeLoopback() && itf.Flags&net.FlagLoopback != 0 { + continue + } + if !f.InterfaceFilter(itf.Name) { + continue + } + + addrs, _ := itf.Addrs() + for _, addr := range addrs { + ip := parseNetAddr(addr) + if ip == nil || !f.IPFilter(ip) { + continue + } + ips = append(ips, ip) + } + } + return +} + +func parseNetAddr(addr net.Addr) net.IP { + switch addr := addr.(type) { + case *net.IPNet: + return addr.IP + case *net.IPAddr: + return addr.IP + } + return nil +} + +func (f *Filters) IncludeLoopback() bool { + return f != nil && f.Loopback +} + +func (f *Filters) InterfaceFilter(name string) bool { + return f == nil || f.Interfaces == nil || slices.Contains(f.Interfaces, name) +} + +func (f *Filters) IPFilter(ip net.IP) bool { + return f == nil || f.IPs == nil || core.Contains(f.IPs, ip.String()) +} + func NewServerAPI(network, address string, filters *Filters) (*webrtc.API, error) { // for debug logs add to env: `PION_LOG_DEBUG=all` m := &webrtc.MediaEngine{} @@ -99,48 +164,17 @@ func NewServerAPI(network, address string, filters *Filters) (*webrtc.API, error _ = s.SetEphemeralUDPPortRange(filters.UDPPorts[0], filters.UDPPorts[1]) } - //if len(hosts) != 0 { - // // support only: host, srflx - // if candidateType, err := webrtc.NewICECandidateType(hosts[0]); err == nil { - // s.SetNAT1To1IPs(hosts[1:], candidateType) - // } else { - // s.SetNAT1To1IPs(hosts, 0) // 0 = host - // } - //} - + // If you don't specify an address, this won't cause an error. + // Connections can still be established using random UDP addresses. if address != "" { + // Both newMux functions respect filters and do not raise an error + // if the port cannot be listened on. if network == "" || network == "tcp" { - if ln, err := net.Listen("tcp", address); err == nil { - tcpMux := webrtc.NewICETCPMux(nil, ln, 8) - s.SetICETCPMux(tcpMux) - } + tcpMux := newTCPMux(address, filters) + s.SetICETCPMux(tcpMux) } - if network == "" || network == "udp" { - // UDPMuxDefault should not listening on unspecified address, use NewMultiUDPMuxFromPort instead - var udpMux ice.UDPMux - if port := xnet.ParseUnspecifiedPort(address); port != 0 { - var networks []ice.NetworkType - for _, ntype := range networkTypes { - networks = append(networks, ice.NetworkType(ntype)) - } - - var err error - if udpMux, err = ice.NewMultiUDPMuxFromPort( - port, - ice.UDPMuxFromPortWithInterfaceFilter(interfaceFilter), - ice.UDPMuxFromPortWithIPFilter(ipFilter), - ice.UDPMuxFromPortWithNetworks(networks...), - ); err != nil { - return nil, err - } - } else { - ln, err := net.ListenPacket("udp", address) - if err != nil { - return nil, err - } - udpMux = ice.NewUDPMuxDefault(ice.UDPMuxParams{UDPConn: ln}) - } + udpMux := newUDPMux(address, filters) s.SetICEUDPMux(udpMux) } } @@ -152,6 +186,57 @@ func NewServerAPI(network, address string, filters *Filters) (*webrtc.API, error ), nil } +// OnNewListener temporary ugly solution for log +var OnNewListener = func(ln any) {} + +func newTCPMux(address string, filters *Filters) ice.TCPMux { + networkTCP := filters.Network("tcp") // tcp or tcp4 or tcp6 + if ln, _ := net.Listen(networkTCP, address); ln != nil { + OnNewListener(ln) + return webrtc.NewICETCPMux(nil, ln, 8) + } + return nil +} + +func newUDPMux(address string, filters *Filters) ice.UDPMux { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil + } + + // UDPMux should not listening on unspecified address. + // So we will create a listener on all available interfaces. + // We can't use ice.NewMultiUDPMuxFromPort, because it sometimes crashes with an error: + // listen udp [***]:8555: bind: cannot assign requested address + var addrs []string + if host == "" { + for _, ip := range filters.NetIPs() { + addrs = append(addrs, fmt.Sprintf("%s:%s", ip, port)) + } + } else { + addrs = []string{address} + } + + networkUDP := filters.Network("udp") // udp or udp4 or udp6 + + var muxes []ice.UDPMux + for _, addr := range addrs { + if ln, _ := net.ListenPacket(networkUDP, addr); ln != nil { + OnNewListener(ln) + mux := ice.NewUDPMuxDefault(ice.UDPMuxParams{UDPConn: ln}) + muxes = append(muxes, mux) + } + } + + switch len(muxes) { + case 0: + return nil + case 1: + return muxes[0] + } + return ice.NewMultiUDPMuxDefault(muxes...) +} + func RegisterDefaultCodecs(m *webrtc.MediaEngine) error { for _, codec := range []webrtc.RTPCodecParameters{ {