github.com/divyam234/rclone@v1.64.1/cmd/serve/dlna/dlna.go (about) 1 // Package dlna provides DLNA server. 2 package dlna 3 4 import ( 5 "bytes" 6 "encoding/xml" 7 "fmt" 8 "net" 9 "net/http" 10 "net/url" 11 "os" 12 "strconv" 13 "strings" 14 "time" 15 16 dms_dlna "github.com/anacrolix/dms/dlna" 17 "github.com/anacrolix/dms/soap" 18 "github.com/anacrolix/dms/ssdp" 19 "github.com/anacrolix/dms/upnp" 20 "github.com/anacrolix/log" 21 "github.com/divyam234/rclone/cmd" 22 "github.com/divyam234/rclone/cmd/serve/dlna/data" 23 "github.com/divyam234/rclone/cmd/serve/dlna/dlnaflags" 24 "github.com/divyam234/rclone/fs" 25 "github.com/divyam234/rclone/vfs" 26 "github.com/divyam234/rclone/vfs/vfsflags" 27 "github.com/spf13/cobra" 28 ) 29 30 func init() { 31 dlnaflags.AddFlags(Command.Flags()) 32 vfsflags.AddFlags(Command.Flags()) 33 } 34 35 // Command definition for cobra. 36 var Command = &cobra.Command{ 37 Use: "dlna remote:path", 38 Short: `Serve remote:path over DLNA`, 39 Long: `Run a DLNA media server for media stored in an rclone remote. Many 40 devices, such as the Xbox and PlayStation, can automatically discover 41 this server in the LAN and play audio/video from it. VLC is also 42 supported. Service discovery uses UDP multicast packets (SSDP) and 43 will thus only work on LANs. 44 45 Rclone will list all files present in the remote, without filtering 46 based on media formats or file extensions. Additionally, there is no 47 media transcoding support. This means that some players might show 48 files that they are not able to play back correctly. 49 50 ` + dlnaflags.Help + vfs.Help, 51 Annotations: map[string]string{ 52 "versionIntroduced": "v1.46", 53 "groups": "Filter", 54 }, 55 Run: func(command *cobra.Command, args []string) { 56 cmd.CheckArgs(1, 1, command, args) 57 f := cmd.NewFsSrc(args) 58 59 cmd.Run(false, false, command, func() error { 60 s, err := newServer(f, &dlnaflags.Opt) 61 if err != nil { 62 return err 63 } 64 if err := s.Serve(); err != nil { 65 return err 66 } 67 s.Wait() 68 return nil 69 }) 70 }, 71 } 72 73 const ( 74 serverField = "Linux/3.4 DLNADOC/1.50 UPnP/1.0 DMS/1.0" 75 rootDescPath = "/rootDesc.xml" 76 resPath = "/r/" 77 serviceControlURL = "/ctl" 78 ) 79 80 type server struct { 81 // The service SOAP handler keyed by service URN. 82 services map[string]UPnPService 83 84 Interfaces []net.Interface 85 86 HTTPConn net.Listener 87 httpListenAddr string 88 handler http.Handler 89 90 RootDeviceUUID string 91 92 FriendlyName string 93 94 // For waiting on the listener to close 95 waitChan chan struct{} 96 97 // Time interval between SSPD announces 98 AnnounceInterval time.Duration 99 100 f fs.Fs 101 vfs *vfs.VFS 102 } 103 104 func newServer(f fs.Fs, opt *dlnaflags.Options) (*server, error) { 105 friendlyName := opt.FriendlyName 106 if friendlyName == "" { 107 friendlyName = makeDefaultFriendlyName() 108 } 109 interfaces := make([]net.Interface, 0, len(opt.InterfaceNames)) 110 for _, interfaceName := range opt.InterfaceNames { 111 var err error 112 intf, err := net.InterfaceByName(interfaceName) 113 if err != nil { 114 return nil, fmt.Errorf("failed to resolve interface name '%s': %w", interfaceName, err) 115 } 116 if !isAppropriatelyConfigured(*intf) { 117 return nil, fmt.Errorf("interface '%s' is not appropriately configured (it should be UP, MULTICAST and MTU > 0)", interfaceName) 118 } 119 interfaces = append(interfaces, *intf) 120 } 121 if len(interfaces) == 0 { 122 interfaces = listInterfaces() 123 } 124 125 s := &server{ 126 AnnounceInterval: opt.AnnounceInterval, 127 FriendlyName: friendlyName, 128 RootDeviceUUID: makeDeviceUUID(friendlyName), 129 Interfaces: interfaces, 130 131 httpListenAddr: opt.ListenAddr, 132 133 f: f, 134 vfs: vfs.New(f, &vfsflags.Opt), 135 } 136 137 s.services = map[string]UPnPService{ 138 "ContentDirectory": &contentDirectoryService{ 139 server: s, 140 }, 141 "ConnectionManager": &connectionManagerService{ 142 server: s, 143 }, 144 "X_MS_MediaReceiverRegistrar": &mediaReceiverRegistrarService{ 145 server: s, 146 }, 147 } 148 149 // Setup the various http routes. 150 r := http.NewServeMux() 151 r.Handle(resPath, http.StripPrefix(resPath, 152 http.HandlerFunc(s.resourceHandler))) 153 if opt.LogTrace { 154 r.Handle(rootDescPath, traceLogging(http.HandlerFunc(s.rootDescHandler))) 155 r.Handle(serviceControlURL, traceLogging(http.HandlerFunc(s.serviceControlHandler))) 156 } else { 157 r.HandleFunc(rootDescPath, s.rootDescHandler) 158 r.HandleFunc(serviceControlURL, s.serviceControlHandler) 159 } 160 r.Handle("/static/", http.StripPrefix("/static/", 161 withHeader("Cache-Control", "public, max-age=86400", 162 http.FileServer(data.Assets)))) 163 s.handler = logging(withHeader("Server", serverField, r)) 164 165 return s, nil 166 } 167 168 // UPnPService is the interface for the SOAP service. 169 type UPnPService interface { 170 Handle(action string, argsXML []byte, r *http.Request) (respArgs map[string]string, err error) 171 Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error) 172 Unsubscribe(sid string) error 173 } 174 175 // Formats the server as a string (used for logging.) 176 func (s *server) String() string { 177 return fmt.Sprintf("DLNA server on %v", s.httpListenAddr) 178 } 179 180 // Returns rclone version number as the model number. 181 func (s *server) ModelNumber() string { 182 return fs.Version 183 } 184 185 // Renders the root device descriptor. 186 func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) { 187 tmpl, err := data.GetTemplate() 188 if err != nil { 189 serveError(s, w, "Failed to load root descriptor template", err) 190 return 191 } 192 193 buffer := new(bytes.Buffer) 194 err = tmpl.Execute(buffer, s) 195 if err != nil { 196 serveError(s, w, "Failed to render root descriptor XML", err) 197 return 198 } 199 200 w.Header().Set("content-type", `text/xml; charset="utf-8"`) 201 w.Header().Set("cache-control", "private, max-age=60") 202 w.Header().Set("content-length", strconv.FormatInt(int64(buffer.Len()), 10)) 203 _, err = buffer.WriteTo(w) 204 if err != nil { 205 // Network error 206 fs.Debugf(s, "Error writing rootDesc: %v", err) 207 } 208 } 209 210 // Handle a service control HTTP request. 211 func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) { 212 soapActionString := r.Header.Get("SOAPACTION") 213 soapAction, err := upnp.ParseActionHTTPHeader(soapActionString) 214 if err != nil { 215 serveError(s, w, "Could not parse SOAPACTION header", err) 216 return 217 } 218 var env soap.Envelope 219 if err := xml.NewDecoder(r.Body).Decode(&env); err != nil { 220 serveError(s, w, "Could not parse SOAP request body", err) 221 return 222 } 223 224 w.Header().Set("Content-Type", `text/xml; charset="utf-8"`) 225 w.Header().Set("Ext", "") 226 soapRespXML, code := func() ([]byte, int) { 227 respArgs, err := s.soapActionResponse(soapAction, env.Body.Action, r) 228 if err != nil { 229 fs.Errorf(s, "Error invoking %v: %v", soapAction, err) 230 upnpErr := upnp.ConvertError(err) 231 return mustMarshalXML(soap.NewFault("UPnPError", upnpErr)), http.StatusInternalServerError 232 } 233 return marshalSOAPResponse(soapAction, respArgs), http.StatusOK 234 }() 235 bodyStr := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8" standalone="yes"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>%s</s:Body></s:Envelope>`, soapRespXML) 236 w.WriteHeader(code) 237 if _, err := w.Write([]byte(bodyStr)); err != nil { 238 fs.Infof(s, "Error writing response: %v", err) 239 } 240 } 241 242 // Handle a SOAP request and return the response arguments or UPnP error. 243 func (s *server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte, r *http.Request) (map[string]string, error) { 244 service, ok := s.services[sa.Type] 245 if !ok { 246 // TODO: What's the invalid service error? 247 return nil, upnp.Errorf(upnp.InvalidActionErrorCode, "Invalid service: %s", sa.Type) 248 } 249 return service.Handle(sa.Action, actionRequestXML, r) 250 } 251 252 // Serves actual resources (media files). 253 func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) { 254 remotePath := r.URL.Path 255 node, err := s.vfs.Stat(r.URL.Path) 256 if err != nil { 257 http.NotFound(w, r) 258 return 259 } 260 261 w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10)) 262 263 // add some DLNA specific headers 264 if r.Header.Get("getContentFeatures.dlna.org") != "" { 265 w.Header().Set("contentFeatures.dlna.org", dms_dlna.ContentFeatures{ 266 SupportRange: true, 267 }.String()) 268 } 269 w.Header().Set("transferMode.dlna.org", "Streaming") 270 271 file := node.(*vfs.File) 272 in, err := file.Open(os.O_RDONLY) 273 if err != nil { 274 serveError(node, w, "Could not open resource", err) 275 return 276 } 277 defer fs.CheckClose(in, &err) 278 279 http.ServeContent(w, r, remotePath, node.ModTime(), in) 280 } 281 282 // Serve runs the server - returns the error only if 283 // the listener was not started; does not block, so 284 // use s.Wait() to block on the listener indefinitely. 285 func (s *server) Serve() (err error) { 286 if s.HTTPConn == nil { 287 // Currently, the SSDP server only listens on an IPv4 multicast address. 288 // Differentiate between two INADDR_ANY addresses, 289 // so that 0.0.0.0 can only listen on IPv4 addresses. 290 network := "tcp4" 291 if strings.Count(s.httpListenAddr, ":") > 1 { 292 network = "tcp" 293 } 294 s.HTTPConn, err = net.Listen(network, s.httpListenAddr) 295 if err != nil { 296 return 297 } 298 } 299 300 go func() { 301 s.startSSDP() 302 }() 303 304 go func() { 305 fs.Logf(s.f, "Serving HTTP on %s", s.HTTPConn.Addr().String()) 306 307 err := s.serveHTTP() 308 if err != nil { 309 fs.Logf(s.f, "Error on serving HTTP server: %v", err) 310 } 311 }() 312 313 return nil 314 } 315 316 // Wait blocks while the listener is open. 317 func (s *server) Wait() { 318 <-s.waitChan 319 } 320 321 func (s *server) Close() { 322 err := s.HTTPConn.Close() 323 if err != nil { 324 fs.Errorf(s.f, "Error closing HTTP server: %v", err) 325 return 326 } 327 close(s.waitChan) 328 } 329 330 // Run SSDP (multicast for server discovery) on all interfaces. 331 func (s *server) startSSDP() { 332 active := 0 333 stopped := make(chan struct{}) 334 for _, intf := range s.Interfaces { 335 active++ 336 go func(intf2 net.Interface) { 337 defer func() { 338 stopped <- struct{}{} 339 }() 340 s.ssdpInterface(intf2) 341 }(intf) 342 } 343 for active > 0 { 344 <-stopped 345 active-- 346 } 347 } 348 349 // Run SSDP server on an interface. 350 func (s *server) ssdpInterface(intf net.Interface) { 351 // Figure out whether should an ip be announced 352 ipfilterFn := func(ip net.IP) bool { 353 listenaddr := s.HTTPConn.Addr().String() 354 listenip := listenaddr[:strings.LastIndex(listenaddr, ":")] 355 switch listenip { 356 case "0.0.0.0": 357 if strings.Contains(ip.String(), ":") { 358 // Any IPv6 address should not be announced 359 // because SSDP only listen on IPv4 multicast address 360 return false 361 } 362 return true 363 case "[::]": 364 // In the @Serve() section, the default settings have been made to not listen on IPv6 addresses. 365 // If actually still listening on [::], then allow to announce any address. 366 return true 367 default: 368 if listenip == ip.String() { 369 return true 370 } 371 return false 372 } 373 } 374 375 // Figure out which HTTP location to advertise based on the interface IP. 376 advertiseLocationFn := func(ip net.IP) string { 377 url := url.URL{ 378 Scheme: "http", 379 Host: (&net.TCPAddr{ 380 IP: ip, 381 Port: s.HTTPConn.Addr().(*net.TCPAddr).Port, 382 }).String(), 383 Path: rootDescPath, 384 } 385 return url.String() 386 } 387 388 _, err := intf.Addrs() 389 if err != nil { 390 panic(err) 391 } 392 fs.Logf(s, "Started SSDP on %v", intf.Name) 393 394 // Note that the devices and services advertised here via SSDP should be 395 // in agreement with the rootDesc XML descriptor that is defined above. 396 ssdpServer := ssdp.Server{ 397 Interface: intf, 398 Devices: []string{ 399 "urn:schemas-upnp-org:device:MediaServer:1"}, 400 Services: []string{ 401 "urn:schemas-upnp-org:service:ContentDirectory:1", 402 "urn:schemas-upnp-org:service:ConnectionManager:1", 403 "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"}, 404 IPFilter: ipfilterFn, 405 Location: advertiseLocationFn, 406 Server: serverField, 407 UUID: s.RootDeviceUUID, 408 NotifyInterval: s.AnnounceInterval, 409 Logger: log.Default, 410 } 411 412 // An interface with these flags should be valid for SSDP. 413 const ssdpInterfaceFlags = net.FlagUp | net.FlagMulticast 414 415 if err := ssdpServer.Init(); err != nil { 416 if intf.Flags&ssdpInterfaceFlags != ssdpInterfaceFlags { 417 // Didn't expect it to work anyway. 418 return 419 } 420 if strings.Contains(err.Error(), "listen") { 421 // OSX has a lot of dud interfaces. Failure to create a socket on 422 // the interface are what we're expecting if the interface is no 423 // good. 424 return 425 } 426 fs.Errorf(s, "Error creating ssdp server on %s: %s", intf.Name, err) 427 return 428 } 429 defer ssdpServer.Close() 430 fs.Infof(s, "Started SSDP on %v", intf.Name) 431 stopped := make(chan struct{}) 432 go func() { 433 defer close(stopped) 434 if err := ssdpServer.Serve(); err != nil { 435 fs.Errorf(s, "%q: %q\n", intf.Name, err) 436 } 437 }() 438 select { 439 case <-s.waitChan: 440 // Returning will close the server. 441 case <-stopped: 442 } 443 } 444 445 func (s *server) serveHTTP() error { 446 srv := &http.Server{ 447 Handler: s.handler, 448 } 449 err := srv.Serve(s.HTTPConn) 450 select { 451 case <-s.waitChan: 452 return nil 453 default: 454 return err 455 } 456 }