129 lines
3.7 KiB

package upnp_ssdp
* Original Code Copyright (C) 2022 Blender Foundation.
* This file is part of Flamenco.
* Flamenco is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
* ***** END GPL LICENSE BLOCK ***** */
import (
// Server advertises services via UPnP/SSDP.
type Server struct {
ssdp *gossdp.Ssdp
log *zerolog.Logger
wrappedLog *ssdpLogger
func NewServer(logger zerolog.Logger) (*Server, error) {
wrap := wrappedLogger(&logger)
ssdp, err := gossdp.NewSsdpWithLogger(nil, wrap)
if err != nil {
return nil, err
return &Server{ssdp, &logger, wrap}, nil
// AddAdvertisement adds a service advertisement for Flamenco Manager.
// Must be called before calling Run().
func (s *Server) AddAdvertisement(serviceLocation string) {
// Define the service we want to advertise
serverDef := gossdp.AdvertisableServer{
ServiceType: FlamencoServiceType,
DeviceUuid: FlamencoUUID,
Location: serviceLocation,
MaxAge: 3600, // Number of seconds this advertisement is valid for.
s.log.Debug().Str("location", serviceLocation).Msg("UPnP/SSDP location registered")
// AddAdvertisementURLs constructs a service location from the given URLs, and
// adds the advertisement for it.
func (s *Server) AddAdvertisementURLs(baseURLs []url.URL) {
for _, url := range baseURLs {
url.Path = path.Join(url.Path, serviceDescriptionPath)
// Run starts the advertisement, and blocks until the context is closed.
func (s *Server) Run(ctx context.Context) {
s.log.Info().Msg("UPnP/SSDP advertisement starting")
isStopping := false
go func() {
// There is a bug in the SSDP library, where closing the server can cause a panic.
defer func() {
if isStopping {
// Only capture a panic when we expect one.
value := recover()
s.log.Debug().Interface("value", value).Msg("recovered from panic in SSDP library")
s.log.Debug().Msg("UPnP/SSDP advertisement stopping")
// Sneakily disable warnings when shutting down, otherwise the read operation
// from the UDP socket will cause a warning.
tempLog := s.log.Level(zerolog.ErrorLevel)
s.wrappedLog.zlog = &tempLog
isStopping = true
s.wrappedLog.zlog = s.log
s.log.Info().Msg("UPnP/SSDP advertisement stopped")
func (s *Server) Description() Description {
return Description{
SpecVersion: SpecVersion{1, 0},
URLBase: "/",
Device: Device{
DeviceType: FlamencoServiceType,
FriendlyName: appinfo.FormattedApplicationInfo(),
ModelName: appinfo.ApplicationName,
ModelDescription: "Flamenco render farm, Manager component",
ModelURL: "https://flamenco.io/",
UDN: fmt.Sprintf("uuid:%s", FlamencoUUID),
Manufacturer: "Blender",
ManufacturerURL: "https://www.blender.org/",
ServiceList: []string{},
PresentationURL: "/",
func (s *Server) DescriptionPath() string {
return serviceDescriptionPath