Browse Source

Windows support with ipconfig

This commit adds Windows support through the ipconfig crate, which determines the system default nameserver to use on Windows, where /etc/resolv.conf isn't a thing.
Benjamin Sago 3 years ago
parent
commit
72cb1e15de
4 changed files with 94 additions and 2 deletions
  1. 2 0
      .travis.yml
  2. 40 0
      Cargo.lock
  3. 4 0
      Cargo.toml
  4. 48 2
      src/resolve.rs

+ 2 - 0
.travis.yml

@@ -7,9 +7,11 @@ rust:
 
 script:
   - cargo build --verbose --workspace
+  - cargo test --verbose --workspace --no-run
   - cargo test --verbose --workspace
 
 os:
+  - windows
   - linux
   - osx
 

+ 40 - 0
Cargo.lock

@@ -167,6 +167,7 @@ dependencies = [
  "dns",
  "dns-transport",
  "getopts",
+ "ipconfig",
  "log",
  "pretty_assertions",
  "rand",
@@ -253,6 +254,18 @@ version = "1.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
 
+[[package]]
+name = "ipconfig"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
+dependencies = [
+ "socket2",
+ "widestring",
+ "winapi",
+ "winreg",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.6"
@@ -584,6 +597,18 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "socket2"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.48"
@@ -645,6 +670,12 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
+[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -666,3 +697,12 @@ name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi",
+]

+ 4 - 0
Cargo.toml

@@ -49,6 +49,10 @@ serde_json = "1.0"
 # logging
 log = "0.4"
 
+# windows default nameserver determination
+[target.'cfg(windows)'.dependencies]
+ipconfig = { version = "0.2" }
+
 [build-dependencies]
 datetime = { version = "0.5.1", default_features = false }
 regex = { version = "1.3", default_features = false, features = ["std"] }

+ 48 - 2
src/resolve.rs

@@ -13,13 +13,17 @@ pub enum Resolver {
     /// Read the list of nameservers from the system, and use that.
     SystemDefault,
 
-    // Use a resolver specified by the user.
+    // Use a specific nameserver specified by the user.
     Specified(Nameserver),
 }
 
 pub type Nameserver = String;
 
 impl Resolver {
+
+    /// Returns a nameserver that queries should be sent to, possibly by
+    /// obtaining one based on the system, returning an error if there was a
+    /// problem looking one up.
     pub fn lookup(self) -> io::Result<Option<Nameserver>> {
         match self {
             Self::Specified(ns)  => Ok(Some(ns)),
@@ -29,6 +33,10 @@ impl Resolver {
 }
 
 
+/// Looks up the system default nameserver on Unix, by querying
+/// `/etc/resolv.conf` and returning the first line that specifies one.
+/// Returns an error if there’s a problem reading the file, or `None` if no
+/// nameserver is specified in the file.
 #[cfg(unix)]
 fn system_nameservers() -> io::Result<Option<Nameserver>> {
     use std::io::{BufRead, BufReader};
@@ -54,7 +62,45 @@ fn system_nameservers() -> io::Result<Option<Nameserver>> {
     Ok(nameservers.first().cloned())
 }
 
-#[cfg(not(unix))]
+
+/// Looks up the system default nameserver on Windows, by iterating through
+/// the list of network adapters and returning the first nameserver it finds.
+#[cfg(windows)]
+fn system_nameservers() -> io::Result<Option<Nameserver>> {
+    let adapters = match ipconfig::get_adapters() {
+        Ok(a) => a,
+        Err(e) => {
+            warn!("Error getting network adapters: {}", e);
+            return Ok(None);
+        }
+    };
+
+    let first_adapter = match adapters.first() {
+        Some(a) => a,
+        None => {
+            warn!("No network adapters available");
+            return Ok(None);
+        }
+    };
+    debug!("Found network adapter {:?}", first_adapter.adapter_name());
+
+    let first_nameserver = match first_adapter.dns_servers().first() {
+        Some(ns) => ns,
+        None => {
+            warn!("No nameservers available");
+            return Ok(None);
+        }
+    };
+    debug!("Found nameserver {:?}", first_nameserver);
+
+    // todo: have this not be turned into a string, then parsed again later
+    Ok(Some(first_nameserver.to_string()))
+}
+
+
+/// The fall-back system default nameserver determinator that is not very
+/// determined as it returns nothing without actually checking anything.
+#[cfg(all(not(unix), not(windows)))]
 fn system_nameservers() -> io::Result<Option<Nameserver>> {
     warn!("Unable to fetch default nameservers on this platform.");
     Ok(None)