108 $socket =
Utils::assumeNotFalse(@socket_create(AF_INET, SOCK_DGRAM, SOL_UDP), fn() =>
"Socket error: " . trim(socket_strerror(socket_last_error())));
109 Utils::assumeNotFalse(@socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, [
"sec" => 3,
"usec" => 0]),
"Socket error: " . trim(socket_strerror(socket_last_error($socket))));
111 "M-SEARCH * HTTP/1.1\r\n" .
113 "HOST: 239.255.255.250:1900\r\n" .
114 "MAN: \"ssdp:discover\"\r\n" .
115 "ST: upnp:rootdevice\r\n\r\n";
117 for($i = 0; $i < self::MAX_DISCOVERY_ATTEMPTS; ++$i){
118 $sendbyte = @socket_sendto($socket, $contents, strlen($contents), 0,
"239.255.255.250", 1900);
119 if($sendbyte ===
false){
120 throw new UPnPException(
"Socket error: " . trim(socket_strerror(socket_last_error($socket))));
122 if($sendbyte !== strlen($contents)){
123 throw new UPnPException(
"Socket error: Unable to send the entire contents.");
126 if(@socket_recvfrom($socket, $buffer, 1024, 0, $responseHost, $responsePort) ===
false){
127 if(socket_last_error($socket) === SOCKET_ETIMEDOUT){
130 throw new UPnPException(
"Socket error: " . trim(socket_strerror(socket_last_error($socket))));
132 $pregResult = preg_match(
'/location\s*:\s*(.+)\n/i', $buffer, $matches);
133 if($pregResult ===
false){
135 throw self::makePcreError();
137 if($pregResult !== 0){
138 $location = trim($matches[1]);
143 socket_close($socket);
144 if($location ===
null){
145 throw new UPnPException(
"Unable to find the router. Ensure that network discovery is enabled in Control Panel.");
147 $url = parse_url($location);
149 throw new UPnPException(
"Failed to parse the router's url: {$location}");
151 if(!isset($url[
'host'])){
152 throw new UPnPException(
"Failed to recognize the host name from the router's url: {$location}");
154 $urlHost = $url[
'host'];
155 if(!isset($url[
'port'])){
156 throw new UPnPException(
"Failed to recognize the port number from the router's url: {$location}");
158 $urlPort = $url[
'port'];
160 $response = Internet::getURL($location, 3, [], $err);
161 if($response ===
null){
162 throw new UPnPException(
"Unable to access XML: {$err}");
164 if($response->getCode() !== 200){
165 throw new UPnPException(
"Unable to access XML: {$response->getBody()}");
168 $defaultInternalError = libxml_use_internal_errors(
true);
170 $root = new \SimpleXMLElement($response->getBody());
171 }
catch(\Exception $e){
172 throw new UPnPException(
"Broken XML.");
174 libxml_use_internal_errors($defaultInternalError);
175 $root->registerXPathNamespace(
"upnp",
"urn:schemas-upnp-org:device-1-0");
176 $xpathResult = Utils::assumeNotFalse($root->xpath(
177 '//upnp:device[upnp:deviceType="urn:schemas-upnp-org:device:InternetGatewayDevice:1"]' .
178 '/upnp:deviceList/upnp:device[upnp:deviceType="urn:schemas-upnp-org:device:WANDevice:1"]' .
179 '/upnp:deviceList/upnp:device[upnp:deviceType="urn:schemas-upnp-org:device:WANConnectionDevice:1"]' .
180 '/upnp:serviceList/upnp:service[upnp:serviceType="urn:schemas-upnp-org:service:WANIPConnection:1"]' .
182 ),
"xpath query is borked");
184 if($xpathResult ===
null || count($xpathResult) === 0){
185 throw new UPnPException(
"Your router does not support portforwarding");
187 $controlURL = (string) $xpathResult[0];
188 $serviceURL = sprintf(
"%s:%d/%s", $urlHost, $urlPort, $controlURL);
241 Internet::postURL($serviceURL, $contents, 3, $headers);