From 105c744ed2138c85167c2d8b66cb3830694fba24 Mon Sep 17 00:00:00 2001
From: Mario Rugiero <mrugiero@gmail.com>
Date: Sat, 21 Jun 2025 16:07:53 -0300
Subject: [PATCH] feat: add IPv6 support for SSDP

This starts SSDP servers on IPv6 link-local and site-local addresses in
addition to the original server for IPv4.
---
 dlna/dms/dms.go | 25 +++++++++++++---------
 ssdp/ssdp.go    | 56 ++++++++++++++++++++++++++++++++++++-------------
 2 files changed, 56 insertions(+), 25 deletions(-)

diff --git a/dlna/dms/dms.go b/dlna/dms/dms.go
index 26db29e..4c71797 100644
--- a/dlna/dms/dms.go
+++ b/dlna/dms/dms.go
@@ -172,23 +172,28 @@ const ssdpInterfaceFlags = net.FlagUp | net.FlagMulticast
 func (me *Server) doSSDP() {
 	var wg sync.WaitGroup
 	for _, if_ := range me.Interfaces {
-		if_ := if_
-		wg.Add(1)
-		go func() {
-			defer wg.Done()
-			me.ssdpInterface(if_)
-		}()
+		for _, addr := range []string{ssdp.AddrString, ssdp.AddrString6LL, ssdp.AddrString6SL} {
+			if_ := if_
+			addr := addr
+			wg.Add(1)
+			go func() {
+				defer wg.Done()
+				me.ssdpInterface(if_, addr)
+			}()
+		}
 	}
 	wg.Wait()
 }
 
 // Run SSDP server on an interface.
-func (me *Server) ssdpInterface(if_ net.Interface) {
+func (me *Server) ssdpInterface(if_ net.Interface, addrString string) {
 	logger := me.Logger.WithNames("ssdp", if_.Name)
 	s := ssdp.Server{
-		Interface: if_,
-		Devices:   devices(),
-		Services:  serviceTypes(),
+		Interface:  if_,
+		AddrString: addrString,
+		NetAddr:    ssdp.AddrString2NetAdd[addrString],
+		Devices:    devices(),
+		Services:   serviceTypes(),
 		Location: func(ip net.IP) string {
 			return me.location(ip)
 		},
diff --git a/ssdp/ssdp.go b/ssdp/ssdp.go
index 2b83dca..ce49189 100644
--- a/ssdp/ssdp.go
+++ b/ssdp/ssdp.go
@@ -15,17 +15,23 @@ import (
 
 	"github.com/anacrolix/log"
 	"golang.org/x/net/ipv4"
+	"golang.org/x/net/ipv6"
 )
 
 const (
-	AddrString = "239.255.255.250:1900"
-	rootDevice = "upnp:rootdevice"
-	aliveNTS   = "ssdp:alive"
-	byebyeNTS  = "ssdp:byebye"
-	mxMax      = 10
+	AddrString    = "239.255.255.250:1900"
+	AddrString6LL = "[ff02::c]:1900"
+	AddrString6SL = "[ff05::c]:1900"
+	rootDevice    = "upnp:rootdevice"
+	aliveNTS      = "ssdp:alive"
+	byebyeNTS     = "ssdp:byebye"
+	mxMax         = 10
 )
 
 var NetAddr *net.UDPAddr
+var NetAddr6LL *net.UDPAddr
+var NetAddr6SL *net.UDPAddr
+var AddrString2NetAdd map[string]*net.UDPAddr = make(map[string]*net.UDPAddr, 3)
 
 func init() {
 	var err error
@@ -33,6 +39,17 @@ func init() {
 	if err != nil {
 		log.Printf("Could not resolve %s: %s", AddrString, err)
 	}
+	NetAddr6LL, err = net.ResolveUDPAddr("udp6", AddrString6LL)
+	if err != nil {
+		log.Printf("Could not resolve %s: %s", AddrString6LL, err)
+	}
+	NetAddr6SL, err = net.ResolveUDPAddr("udp6", AddrString6SL)
+	if err != nil {
+		log.Printf("Could not resolve %s: %s", AddrString6SL, err)
+	}
+	AddrString2NetAdd[AddrString] = NetAddr
+	AddrString2NetAdd[AddrString6LL] = NetAddr6LL
+	AddrString2NetAdd[AddrString6SL] = NetAddr6SL
 }
 
 type badStringError struct {
@@ -81,6 +98,8 @@ func ReadRequest(b *bufio.Reader) (req *http.Request, err error) {
 type Server struct {
 	conn           *net.UDPConn
 	Interface      net.Interface
+	AddrString     string
+	NetAddr        *net.UDPAddr
 	Server         string
 	Services       []string
 	Devices        []string
@@ -92,14 +111,21 @@ type Server struct {
 	Logger         log.Logger
 }
 
-func makeConn(ifi net.Interface) (ret *net.UDPConn, err error) {
-	ret, err = net.ListenMulticastUDP("udp", &ifi, NetAddr)
+func makeConn(ifi net.Interface, netAddr *net.UDPAddr) (ret *net.UDPConn, err error) {
+	ret, err = net.ListenMulticastUDP("udp", &ifi, netAddr)
 	if err != nil {
 		return
 	}
-	p := ipv4.NewPacketConn(ret)
-	if err := p.SetMulticastTTL(2); err != nil {
-		log.Print(err)
+	if netAddr.IP.String() == AddrString {
+		p := ipv4.NewPacketConn(ret)
+		if err := p.SetMulticastTTL(2); err != nil {
+			log.Print(err)
+		}
+	} else {
+		p := ipv6.NewPacketConn(ret)
+		if err := p.SetMulticastHopLimit(2); err != nil {
+			log.Print(err)
+		}
 	}
 	// if err := p.SetMulticastLoopback(true); err != nil {
 	// 	log.Println(err)
@@ -132,7 +158,7 @@ func (me *Server) serve() {
 
 func (me *Server) Init() (err error) {
 	me.closed = make(chan struct{})
-	me.conn, err = makeConn(me.Interface)
+	me.conn, err = makeConn(me.Interface, me.NetAddr)
 	if me.IPFilter == nil {
 		me.IPFilter = func(net.IP) bool { return true }
 	}
@@ -195,7 +221,7 @@ func (me *Server) usnFromTarget(target string) string {
 
 func (me *Server) makeNotifyMessage(target, nts string, extraHdrs [][2]string) []byte {
 	lines := [...][2]string{
-		{"HOST", AddrString},
+		{"HOST", me.AddrString},
 		{"NT", target},
 		{"NTS", nts},
 		{"SERVER", me.Server},
@@ -246,7 +272,7 @@ func (me *Server) log(args ...interface{}) {
 func (me *Server) sendByeBye() {
 	for _, type_ := range me.allTypes() {
 		buf := me.makeNotifyMessage(type_, byebyeNTS, nil)
-		me.send(buf, NetAddr)
+		me.send(buf, me.NetAddr)
 	}
 }
 
@@ -254,7 +280,7 @@ func (me *Server) notifyAll(nts string, extraHdrs [][2]string) {
 	for _, type_ := range me.allTypes() {
 		buf := me.makeNotifyMessage(type_, nts, extraHdrs)
 		delay := time.Duration(rand.Int63n(int64(100 * time.Millisecond)))
-		me.delayedSend(delay, buf, NetAddr)
+		me.delayedSend(delay, buf, me.NetAddr)
 	}
 }
 
@@ -279,7 +305,7 @@ func (me *Server) handle(buf []byte, sender *net.UDPAddr) {
 		return
 	}
 	var mx int64
-	if req.Header.Get("Host") == AddrString {
+	if req.Header.Get("Host") == me.AddrString {
 		mxHeader := req.Header.Get("mx")
 		i, err := strconv.ParseUint(mxHeader, 0, 0)
 		if err != nil {
