fix(whip): clean up lifecycle — proper net import and checkPortFree implementation
This commit is contained in:
parent
b1057756d2
commit
a22b8c68f0
1 changed files with 13 additions and 24 deletions
|
|
@ -2,6 +2,7 @@ package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
appcfg "github.com/datarhei/core/v16/restream/app"
|
appcfg "github.com/datarhei/core/v16/restream/app"
|
||||||
)
|
)
|
||||||
|
|
@ -103,7 +104,7 @@ func (s *Subsystem) onWHIPProcessStop(id string) {
|
||||||
|
|
||||||
// allocAdjacentPortPair finds two consecutive free loopback UDP ports
|
// allocAdjacentPortPair finds two consecutive free loopback UDP ports
|
||||||
// (V, V+1) and returns V. It retries up to allocAttempts times because
|
// (V, V+1) and returns V. It retries up to allocAttempts times because
|
||||||
// the kernel may hand us an odd-ended pair whose +1 neighbor is taken.
|
// the kernel may hand us a port whose +1 neighbor is already taken.
|
||||||
//
|
//
|
||||||
// The caller owns the returned port numbers; FFmpeg will bind them
|
// The caller owns the returned port numbers; FFmpeg will bind them
|
||||||
// immediately on process start via the ConfigIO input legs.
|
// immediately on process start via the ConfigIO input legs.
|
||||||
|
|
@ -115,14 +116,14 @@ func allocAdjacentPortPair() (int, error) {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Verify the audio port (videoPort+1) is also free.
|
|
||||||
// We do a quick bind-and-release on videoPort+1.
|
|
||||||
audioPort := videoPort + 1
|
audioPort := videoPort + 1
|
||||||
if audioPort > 65535 {
|
if audioPort > 65535 {
|
||||||
lastErr = fmt.Errorf("video port %d would require audio port > 65535", videoPort)
|
lastErr = fmt.Errorf("video port %d would push audio port above 65535", videoPort)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Try to claim videoPort+1 momentarily to verify availability.
|
// Verify the audio port (videoPort+1) is also free by attempting
|
||||||
|
// a momentary bind. TOCTOU race is accepted; FFmpeg will fail-fast
|
||||||
|
// on the actual bind and the process will restart cleanly.
|
||||||
if err := checkPortFree(audioPort); err != nil {
|
if err := checkPortFree(audioPort); err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
continue
|
continue
|
||||||
|
|
@ -135,27 +136,15 @@ func allocAdjacentPortPair() (int, error) {
|
||||||
return 0, fmt.Errorf("after %d attempts: %w", allocAttempts, lastErr)
|
return 0, fmt.Errorf("after %d attempts: %w", allocAttempts, lastErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPortFree does a quick UDP bind-and-release on the given loopback
|
// checkPortFree attempts a momentary UDP bind on the given loopback
|
||||||
// port. Returns nil if the port appears free, non-nil if it is in use.
|
// port. Returns nil if the port appears available, non-nil otherwise.
|
||||||
// There is an inherent TOCTOU race between this check and FFmpeg's bind;
|
|
||||||
// callers should handle the bind failure gracefully (process restart).
|
|
||||||
func checkPortFree(port int) error {
|
func checkPortFree(port int) error {
|
||||||
import_net_listen := func() error {
|
c, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
|
||||||
// Inline the check without importing net again (already in portalloc.go
|
if err != nil {
|
||||||
// via the package-level Alloc function). We re-use Alloc's pattern:
|
return fmt.Errorf("port %d not free: %w", port, err)
|
||||||
// bind :port, get error, close.
|
|
||||||
//
|
|
||||||
// This file is in the same package so we can call the unexported helper.
|
|
||||||
// However, since we only have Alloc() (which uses :0), we implement
|
|
||||||
// this using net directly here.
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
_ = import_net_listen
|
_ = c.Close()
|
||||||
// Use net directly since this package already imports it via portalloc.go.
|
return nil
|
||||||
// We can't import "net" twice, but since this file is in the same package
|
|
||||||
// as portalloc.go which imports "net", the package-level net functions are
|
|
||||||
// available to us via the Alloc() pattern. We implement the check manually:
|
|
||||||
return checkUDPPortFree(port)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildWHIPInputLegs produces the two FFmpeg input ConfigIO legs for
|
// buildWHIPInputLegs produces the two FFmpeg input ConfigIO legs for
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue