diff --git a/src/Config/Config.php b/src/Config/Config.php index 0f3af69e..d7b3a1e4 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -17,7 +17,7 @@ class Config * * Note that this method will try to access its files and/or commands and * try to parse its output. Currently, this will only parse valid nameserver - * entries from `/etc/resolv.conf` and will ignore all other output without + * entries from its output and will ignore all other output without * complaining. * * Note that the previous section implies that this may return an empty @@ -28,6 +28,12 @@ class Config */ public static function loadSystemConfigBlocking() { + // Use WMIC output on Windows + if (DIRECTORY_SEPARATOR === '\\') { + return self::loadWmicBlocking(); + } + + // otherwise (try to) load from resolv.conf try { return self::loadResolvConfBlocking(); } catch (RuntimeException $ignored) { @@ -84,5 +90,38 @@ public static function loadResolvConfBlocking($path = null) return $config; } + /** + * Loads the DNS configurations from Windows's WMIC (from the given command or default command) + * + * Note that this method blocks while loading the given command and should + * thus be used with care! While this should be relatively fast for normal + * WMIC commands, it remains unknown if this may block under certain + * circumstances. In particular, this method should only be executed before + * the loop starts, not while it is running. + * + * Note that this method will only try to execute the given command try to + * parse its output, irrespective of whether this command exists. In + * particular, this command is only available on Windows. Currently, this + * will only parse valid nameserver entries from the command output and will + * ignore all other output without complaining. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid nameserver entries can be found. + * + * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing + * @return self + * @link https://ss64.com/nt/wmic.html + */ + public static function loadWmicBlocking($command = null) + { + $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command); + preg_match_all('/(?:{|,|")([\da-f.:]{4,})(?:}|,|")/i', $contents, $matches); + + $config = new self(); + $config->nameservers = $matches[1]; + + return $config; + } + public $nameservers = array(); } diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index 4480713f..e53c08e5 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -95,4 +95,80 @@ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries() $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents)); $this->assertEquals($expected, $config->nameservers); } + + public function testLoadsFromWmicOnWindows() + { + if (DIRECTORY_SEPARATOR !== '\\') { + $this->markTestSkipped('Only on Windows'); + } + + $config = Config::loadWmicBlocking(); + + $this->assertInstanceOf('React\Dns\Config\Config', $config); + } + + public function testLoadsSingleEntryFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +'; + $expected = array('192.168.2.1'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsEmptyListFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +'; + $expected = array(); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsSingleEntryForMultipleNicsFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{192.168.2.1} +ACE, +ACE,{192.168.2.2} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + public function testLoadsMultipleEntriesForSingleNicFromWmicOutput() + { + $contents = ' +Node,DNSServerSearchOrder +ACE, +ACE,{"192.168.2.1","192.168.2.2"} +ACE, +'; + $expected = array('192.168.2.1', '192.168.2.2'); + + $config = Config::loadWmicBlocking($this->echoCommand($contents)); + + $this->assertEquals($expected, $config->nameservers); + } + + private function echoCommand($output) + { + return 'echo ' . escapeshellarg($output); + } }