github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/ui/discovery/ssdp.go (about) 1 /* 2 * Copyright (C) 2019 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package discovery 19 20 import ( 21 "bytes" 22 "fmt" 23 "net" 24 "net/http" 25 "net/url" 26 "runtime" 27 "sync" 28 "text/template" 29 "time" 30 31 "github.com/gofrs/uuid" 32 "github.com/koron/go-ssdp" 33 "github.com/mysteriumnetwork/node/core/ip" 34 "github.com/mysteriumnetwork/node/metadata" 35 "github.com/mysteriumnetwork/node/requests" 36 "github.com/pkg/errors" 37 "github.com/rs/zerolog/log" 38 ) 39 40 const deviceDescriptionTemplate = `<?xml version="1.0"?> 41 <root xmlns="urn:schemas-upnp-org:device-1-0" configId="1"> 42 <specVersion> 43 <major>1</major> 44 <minor>1</minor> 45 </specVersion> 46 <device> 47 <deviceType>urn:schemas-upnp-org:device:node:1</deviceType> 48 <friendlyName>Mysterium Node</friendlyName> 49 50 <manufacturer>Mysterium Network</manufacturer> 51 <manufacturerURL>https://mysterium.network/</manufacturerURL> 52 53 <modelName>Raspberry Node</modelName> 54 <modelNumber>{{.Version}}</modelNumber> 55 <modelURL>https://mysterium.network/node/</modelURL> 56 57 <UDN>uuid:{{.UUID}}</UDN> 58 <presentationURL>{{.URL}}</presentationURL> 59 </device> 60 </root>` 61 62 type ssdpServer struct { 63 uiPort int 64 uuid string 65 ssdp *ssdp.Advertiser 66 quit chan struct{} 67 once sync.Once 68 httpClient *requests.HTTPClient 69 } 70 71 func newSSDPServer(uiPort int, httpClient *requests.HTTPClient) *ssdpServer { 72 return &ssdpServer{ 73 uiPort: uiPort, 74 quit: make(chan struct{}), 75 httpClient: httpClient, 76 } 77 } 78 79 func (ss *ssdpServer) Start() (err error) { 80 ss.uuid, err = generateUUID() 81 if err != nil { 82 return errors.Wrap(err, "failed to generate new UUID") 83 } 84 85 url, err := ss.serveDeviceDescriptionDocument() 86 if err != nil { 87 return errors.Wrap(err, "failed to serve device description document") 88 } 89 90 ss.ssdp, err = ssdp.Advertise( 91 "upnp:rootdevice", // ST: Type 92 "uuid:"+ss.uuid+"::upnp:rootdevice", // USN: ID 93 url.String(), // LOCATION: location header 94 runtime.GOOS+" UPnP/1.1 node/"+metadata.VersionAsString(), // SERVER: server header 95 1800) // cache control, max-age. A duration for which the advertisement is valid 96 if err != nil { 97 return errors.Wrap(err, "failed to start SSDP advertiser") 98 } 99 100 if ss.ssdpPubliclyAccessible() { 101 log.Warn().Msg("SSDP publicly accessible. Stopping it to prevent abusing service") 102 err := ss.Stop() 103 if err != nil { 104 log.Error().Err(err).Msg("Failed to stop SSDP") 105 } 106 } 107 108 for { 109 select { 110 case <-time.After(30 * time.Second): 111 if err := ss.ssdp.Alive(); err != nil { 112 log.Warn().Err(err).Msg("Failed to sent SSDP Alive message") 113 } 114 case <-ss.quit: 115 return nil 116 } 117 } 118 } 119 120 func (ss *ssdpServer) Stop() error { 121 var closeError error 122 ss.once.Do(func() { 123 close(ss.quit) 124 if ss.ssdp != nil { 125 if err := ss.ssdp.Bye(); err != nil { 126 log.Error().Err(err).Msg("Failed to send SSDP bye message") 127 } 128 closeError = errors.Wrap(ss.ssdp.Close(), "failed to send SSDP bye message") 129 } 130 }) 131 132 return closeError 133 } 134 135 func (ss *ssdpServer) serveDeviceDescriptionDocument() (url.URL, error) { 136 resolver := ip.NewResolver(ss.httpClient, "0.0.0.0", "", ip.IPFallbackAddresses) 137 138 outIP, err := resolver.GetOutboundIP() 139 if err != nil { 140 return url.URL{}, err 141 } 142 143 listener, err := net.Listen("tcp", ":0") 144 if err != nil { 145 return url.URL{}, err 146 } 147 148 deviceDoc := ss.deviceDescription(outIP) 149 150 mux := http.NewServeMux() 151 mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 152 fmt.Fprint(w, deviceDoc) 153 }) 154 155 go func() { 156 go http.Serve(listener, mux) 157 <-ss.quit 158 listener.Close() 159 }() 160 161 port := listener.Addr().(*net.TCPAddr).Port 162 return url.URL{ 163 Scheme: "http", 164 Host: fmt.Sprintf("%s:%d", outIP, port), 165 }, nil 166 } 167 168 func (ss *ssdpServer) deviceDescription(ip string) string { 169 var buf bytes.Buffer 170 deviceDescription := template.Must(template.New("SSDPDeviceDescription").Parse(deviceDescriptionTemplate)) 171 _ = deviceDescription.Execute(&buf, 172 struct{ URL, Version, UUID string }{ 173 fmt.Sprintf("http://%s:%d/", ip, ss.uiPort), 174 metadata.VersionAsString(), 175 ss.uuid, 176 }) 177 178 return buf.String() 179 } 180 181 func (ss *ssdpServer) ssdpPubliclyAccessible() bool { 182 // https://github.com/cloudflare/badupnp 183 // https://blog.cloudflare.com/ssdp-100gbps/ 184 // > Furthermore, we prepared on online checking website. 185 // > Click if you want to know if your public IP address has a vulnerable SSDP service: 186 // > https://badupnp.benjojo.co.uk/ 187 188 req, err := requests.NewGetRequest("https://badupnp.benjojo.co.uk", "test", nil) 189 if err != nil { 190 log.Error().Err(err).Msg("Failed to create HTTP request to test a vulnerable SSDP service") 191 return false 192 } 193 194 var result struct { 195 Result bool `json:"result"` 196 } 197 198 err = ss.httpClient.DoRequestAndParseResponse(req, &result) 199 if err != nil { 200 log.Error().Err(err).Msg("Failed to detect IP has a vulnerable SSDP service") 201 return false 202 } 203 204 return result.Result 205 } 206 207 func generateUUID() (string, error) { 208 uid, err := uuid.NewV4() 209 if err != nil { 210 return "", err 211 } 212 213 return uid.String(), nil 214 }