feat(stream): add NAT-PMP port mapping for remote downloads
Replace anacrolix/upnp with huin/goupnp + custom NAT-PMP (RFC 6886) implementation. NAT-PMP is tried first (faster, more compatible with TP-Link routers), with UPnP-IGD SOAP as fallback. Gateway detection reads /proc/net/route for accuracy. Includes unit tests with mock NAT-PMP server and permanent e2e tests (build tag manual).
This commit is contained in:
parent
819c727bf5
commit
aa6acbabc9
8 changed files with 1030 additions and 24 deletions
127
internal/engine/upnp_debug_test.go
Normal file
127
internal/engine/upnp_debug_test.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//go:build manual
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp"
|
||||
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||
)
|
||||
|
||||
// TestUPnPDebug performs detailed UPnP discovery diagnostics.
|
||||
// Run with: go test -tags manual -run TestUPnPDebug -v ./internal/engine/
|
||||
func TestUPnPDebug(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fmt.Println("=== UPnP Debug Diagnostics ===")
|
||||
fmt.Println()
|
||||
|
||||
// 1. Check network interfaces
|
||||
fmt.Println("--- Network Interfaces ---")
|
||||
ifaces, _ := net.Interfaces()
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, _ := iface.Addrs()
|
||||
for _, addr := range addrs {
|
||||
fmt.Printf(" %s: %s (flags: %s)\n", iface.Name, addr, iface.Flags)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 2. Raw SSDP discovery — search for ALL UPnP root devices
|
||||
fmt.Println("--- Raw SSDP Discovery (all root devices) ---")
|
||||
devices, err := goupnp.DiscoverDevicesCtx(ctx, "upnp:rootdevice")
|
||||
if err != nil {
|
||||
fmt.Printf(" Error: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d root device(s)\n", len(devices))
|
||||
for i, dev := range devices {
|
||||
if dev.Err != nil {
|
||||
fmt.Printf(" [%d] Error: %v\n", i, dev.Err)
|
||||
continue
|
||||
}
|
||||
rd := dev.Root
|
||||
fmt.Printf(" [%d] %s — %s (%s)\n", i, rd.Device.FriendlyName, rd.Device.DeviceType, rd.URLBase.String())
|
||||
// List services
|
||||
for _, svc := range rd.Device.Services {
|
||||
fmt.Printf(" Service: %s\n", svc.ServiceType)
|
||||
}
|
||||
// List sub-devices
|
||||
for _, sub := range rd.Device.Devices {
|
||||
fmt.Printf(" SubDevice: %s — %s\n", sub.FriendlyName, sub.DeviceType)
|
||||
for _, svc := range sub.Services {
|
||||
fmt.Printf(" Service: %s\n", svc.ServiceType)
|
||||
}
|
||||
for _, sub2 := range sub.Devices {
|
||||
fmt.Printf(" SubDevice: %s — %s\n", sub2.FriendlyName, sub2.DeviceType)
|
||||
for _, svc := range sub2.Services {
|
||||
fmt.Printf(" Service: %s\n", svc.ServiceType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 3. Try specific IGD service types
|
||||
fmt.Println("--- IGD Service Discovery ---")
|
||||
|
||||
fmt.Print(" WANIPConnection2: ")
|
||||
c2, errs2, err2 := internetgateway2.NewWANIPConnection2ClientsCtx(ctx)
|
||||
if err2 != nil {
|
||||
fmt.Printf("error: %v\n", err2)
|
||||
} else {
|
||||
fmt.Printf("%d client(s), %d error(s)\n", len(c2), len(errs2))
|
||||
for _, e := range errs2 {
|
||||
fmt.Printf(" err: %v\n", e)
|
||||
}
|
||||
for _, c := range c2 {
|
||||
ip, err := c.GetExternalIPAddress()
|
||||
fmt.Printf(" device=%s external_ip=%s err=%v\n",
|
||||
c.ServiceClient.RootDevice.Device.FriendlyName, ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(" WANIPConnection1: ")
|
||||
c1, errs1, err1 := internetgateway2.NewWANIPConnection1ClientsCtx(ctx)
|
||||
if err1 != nil {
|
||||
fmt.Printf("error: %v\n", err1)
|
||||
} else {
|
||||
fmt.Printf("%d client(s), %d error(s)\n", len(c1), len(errs1))
|
||||
for _, e := range errs1 {
|
||||
fmt.Printf(" err: %v\n", e)
|
||||
}
|
||||
for _, c := range c1 {
|
||||
ip, err := c.GetExternalIPAddress()
|
||||
fmt.Printf(" device=%s external_ip=%s err=%v\n",
|
||||
c.ServiceClient.RootDevice.Device.FriendlyName, ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(" WANPPPConnection1: ")
|
||||
cp, errsp, errp := internetgateway2.NewWANPPPConnection1ClientsCtx(ctx)
|
||||
if errp != nil {
|
||||
fmt.Printf("error: %v\n", errp)
|
||||
} else {
|
||||
fmt.Printf("%d client(s), %d error(s)\n", len(cp), len(errsp))
|
||||
for _, e := range errsp {
|
||||
fmt.Printf(" err: %v\n", e)
|
||||
}
|
||||
for _, c := range cp {
|
||||
ip, err := c.GetExternalIPAddress()
|
||||
fmt.Printf(" device=%s external_ip=%s err=%v\n",
|
||||
c.ServiceClient.RootDevice.Device.FriendlyName, ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== Done ===")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue