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);
 
 
  242        Internet::postURL($serviceURL, $contents, 3, $headers);