diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs index f60e7b153dc..b6f48f01d3a 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs @@ -4,6 +4,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker { using System; using System.Collections.Generic; + using System.IO; + using System.Runtime.InteropServices; using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Docker; @@ -78,7 +80,7 @@ void MountSockets(IModule module, CreateContainerParameters createOptions) SetMountOptions(createOptions, workloadUri); } - // If Management URI is Unix domain socket, and the module is the EdgeAgent, then mount it ino the container. + // If Management URI is Unix domain socket, and the module is the EdgeAgent, then mount it into the container. var managementUri = new Uri(this.configSource.Configuration.GetValue(Constants.EdgeletManagementUriVariableName)); if (managementUri.Scheme == "unix" && module.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) @@ -91,10 +93,20 @@ static void SetMountOptions(CreateContainerParameters createOptions, Uri uri) { HostConfig hostConfig = createOptions.HostConfig ?? new HostConfig(); IList binds = hostConfig.Binds ?? new List(); - binds.Add($"{uri.AbsolutePath}:{uri.AbsolutePath}"); + string path = BindPath(uri); + binds.Add($"{path}:{path}"); hostConfig.Binds = binds; createOptions.HostConfig = hostConfig; } + + static String BindPath(Uri uri) + { + // On Windows we need to bind to the parent folder. We can't bind + // directly to the socket file. + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.GetDirectoryName(uri.LocalPath) + : uri.AbsolutePath; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs index 71f0d223a80..9a1b16aa313 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs @@ -2,6 +2,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test { using System; using System.Collections.Generic; + using System.Runtime.InteropServices; using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Docker; @@ -31,12 +32,21 @@ public void TestVolMount() module.SetupGet(m => m.Config).Returns(new DockerConfig("nginx:latest")); module.SetupGet(m => m.Name).Returns(Constants.EdgeAgentModuleName); + var unixUris = new Dictionary + { + {Constants.EdgeletWorkloadUriVariableName, "unix:///path/to/workload.sock" }, + {Constants.EdgeletManagementUriVariableName, "unix:///path/to/mgmt.sock" } + }; + + var windowsUris = new Dictionary + { + {Constants.EdgeletWorkloadUriVariableName, "unix:///C:/path/to/workload/sock" }, + {Constants.EdgeletManagementUriVariableName, "unix:///C:/path/to/mgmt/sock" } + }; + IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( - new Dictionary - { - {Constants.EdgeletWorkloadUriVariableName, "unix:///var/run/iotedgedworkload.sock" }, - {Constants.EdgeletManagementUriVariableName, "unix:///var/run/iotedgedmgmt.sock" } - }).Build(); + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windowsUris : unixUris + ).Build(); var configSource = Mock.Of(s => s.Configuration == configRoot); ICombinedConfigProvider provider = new CombinedEdgeletConfigProvider(new[] { new AuthConfig() }, configSource); @@ -48,8 +58,14 @@ public void TestVolMount() Assert.NotNull(config.CreateOptions.HostConfig); Assert.NotNull(config.CreateOptions.HostConfig.Binds); Assert.Equal(2, config.CreateOptions.HostConfig.Binds.Count); - Assert.Equal("/var/run/iotedgedworkload.sock:/var/run/iotedgedworkload.sock", config.CreateOptions.HostConfig.Binds[0]); - Assert.Equal("/var/run/iotedgedmgmt.sock:/var/run/iotedgedmgmt.sock", config.CreateOptions.HostConfig.Binds[1]); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Equal("C:\\path\\to\\workload:C:\\path\\to\\workload", config.CreateOptions.HostConfig.Binds[0]); + Assert.Equal("C:\\path\\to\\mgmt:C:\\path\\to\\mgmt", config.CreateOptions.HostConfig.Binds[1]); + } else { + Assert.Equal("/path/to/workload.sock:/path/to/workload.sock", config.CreateOptions.HostConfig.Binds[0]); + Assert.Equal("/path/to/mgmt.sock:/path/to/mgmt.sock", config.CreateOptions.HostConfig.Binds[1]); + } } [Fact] diff --git a/edgelet/Cargo.lock b/edgelet/Cargo.lock index d68a0f0fe13..6aba08d550b 100644 --- a/edgelet/Cargo.lock +++ b/edgelet/Cargo.lock @@ -376,9 +376,11 @@ dependencies = [ "hyper-proxy 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyperlocal 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyperlocal-windows 0.1.0 (git+https://github.com/Azure/hyperlocal-windows)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds-windows 0.1.0 (git+https://github.com/Azure/mio-uds-windows.git)", "nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -387,12 +389,15 @@ dependencies = [ "serde 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "systemd 0.1.0", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-named-pipe 0.1.0", "tokio-uds 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-uds-windows 0.1.0 (git+https://github.com/Azure/tokio-uds-windows.git)", "typed-headers 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -475,8 +480,10 @@ dependencies = [ "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.10 (registry+https://github.com/rust-lang/crates.io-index)", "hyperlocal 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyperlocal-windows 0.1.0 (git+https://github.com/Azure/hyperlocal-windows)", "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds-windows 0.1.0 (git+https://github.com/Azure/mio-uds-windows.git)", "serde 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -750,6 +757,20 @@ dependencies = [ "tokio-uds 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hyperlocal-windows" +version = "0.1.0" +source = "git+https://github.com/Azure/hyperlocal-windows#2bd432bbbfb5b1cf38429733dd9a593c7b97a850" +dependencies = [ + "futures 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds-windows 0.1.0 (git+https://github.com/Azure/mio-uds-windows.git)", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-uds-windows 0.1.0 (git+https://github.com/Azure/tokio-uds-windows.git)", +] + [[package]] name = "idna" version = "0.1.4" @@ -889,6 +910,11 @@ name = "lazycell" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazycell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.42" @@ -1013,6 +1039,21 @@ dependencies = [ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio-uds-windows" +version = "0.1.0" +source = "git+https://github.com/Azure/mio-uds-windows.git#67c2c785112b79ff212c018f124196d7297c5d88" +dependencies = [ + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miow" version = "0.2.1" @@ -1788,6 +1829,21 @@ dependencies = [ "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-uds-windows" +version = "0.1.0" +source = "git+https://github.com/Azure/tokio-uds-windows.git#b689a914dbaa905f359f89200c01fed7a6c8df3f" +dependencies = [ + "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds-windows 0.1.0 (git+https://github.com/Azure/mio-uds-windows.git)", + "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.4.6" @@ -2086,6 +2142,7 @@ dependencies = [ "checksum hyper-proxy 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ca88dbc8a46e9a7530321ae8196527673d24f6ad08ce5b35078fe6a7d4001c6" "checksum hyper-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "caaee4dea92794a9e697038bd401e264307d1f22c883dbcb6f6618ba0d3b3bd3" "checksum hyperlocal 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d063d6d5658623c6ef16f452e11437c0e7e23a6d327470573fe78892dafbc4fb" +"checksum hyperlocal-windows 0.1.0 (git+https://github.com/Azure/hyperlocal-windows)" = "" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08173ba1e906efb6538785a8844dd496f5d34f0a2d88038e95195172fc667220" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" @@ -2094,6 +2151,7 @@ dependencies = [ "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" +"checksum lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddba4c30a78328befecec92fc94970e53b3ae385827d28620f0f5bb2493081e0" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" @@ -2107,6 +2165,7 @@ dependencies = [ "checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" "checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum mio-uds-windows 0.1.0 (git+https://github.com/Azure/mio-uds-windows.git)" = "" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum miow 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9224c91f82b3c47cf53dcf78dfaa20d6888fbcc5d272d5f2fcdf8a697f3c987d" "checksum native-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0a7bd714e83db15676d31caf968ad7318e9cc35f93c85a90231c8f22867549" @@ -2187,6 +2246,7 @@ dependencies = [ "checksum tokio-tls 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e53fdbf3156f588be1676022fe794232b24922d426e8c14f4e46891c1e31c440" "checksum tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "137bda266504893ac4774e0ec4c2108f7ccdbcb7ac8dced6305fe9e4e0b5041a" "checksum tokio-uds 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22e3aa6d1fcc19e635418dc0a30ab5bd65d347973d6f43f1a37bf8d9d1335fc9" +"checksum tokio-uds-windows 0.1.0 (git+https://github.com/Azure/tokio-uds-windows.git)" = "" "checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum typed-headers 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fb9c89066fda8824c7c9570d0f5c0dddd2a0b272b189855f89450e830f196eb" diff --git a/edgelet/contrib/config/windows/config.yaml b/edgelet/contrib/config/windows/config.yaml index 751ed0aec80..59162a52923 100644 --- a/edgelet/contrib/config/windows/config.yaml +++ b/edgelet/contrib/config/windows/config.yaml @@ -102,12 +102,20 @@ hostname: "" # # The following uri schemes are supported: # http - connect over TCP +# unix - connect over Unix domain socket +# +# If the 'unix' scheme is selected, the daemon expects that the parent +# directory of the specified socket file already exists, and that the Windows +# group 'NT AUTHORITY\Authenticated Users' has been given 'Modify' rights on +# the directory. For example, if the URI "unix:///C:/path/to/sock.file" is +# specified, then the directory "C:\path\to" must exist with the correct +# permissions. # ############################################################################### connect: - management_uri: "http://:15580" - workload_uri: "http://:15581" + management_uri: "unix:///C:/ProgramData/iotedge/mgmt/sock" + workload_uri: "unix:///C:/ProgramData/iotedge/workload/sock" ############################################################################### # Listen settings @@ -120,12 +128,20 @@ connect: # # The following uri schemes are supported: # http - listen over TCP +# unix - listen over Unix domain socket +# +# If the 'unix' scheme is selected, the daemon expects that the parent +# directory of the specified socket file already exists, and that the Windows +# group 'NT AUTHORITY\Authenticated Users' has been given 'Modify' rights on +# the directory. For example, if the URI "unix:///C:/path/to/sock.file" is +# specified, then the directory "C:\path\to" must exist with the correct +# permissions. # ############################################################################### listen: - management_uri: "http://:15580" - workload_uri: "http://:15581" + management_uri: "unix:///C:/ProgramData/iotedge/mgmt/sock" + workload_uri: "unix:///C:/ProgramData/iotedge/workload/sock" ############################################################################### # Home Directory diff --git a/edgelet/edgelet-docker/src/runtime.rs b/edgelet/edgelet-docker/src/runtime.rs index 34b4a405090..91878d2f440 100644 --- a/edgelet/edgelet-docker/src/runtime.rs +++ b/edgelet/edgelet-docker/src/runtime.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::convert::From; use std::ops::Deref; +use std::path::PathBuf; use std::time::Duration; use base64; @@ -22,7 +23,7 @@ use edgelet_core::{ LogOptions, Module, ModuleRegistry, ModuleRuntime, ModuleRuntimeState, ModuleSpec, SystemInfo as CoreSystemInfo, }; -use edgelet_http::UrlConnector; +use edgelet_http::{UrlConnector, UrlExt}; use edgelet_utils::log_failure; use error::{Error, ErrorKind, Result}; @@ -53,9 +54,12 @@ impl DockerModuleRuntime { let client = Client::builder().build(UrlConnector::new(docker_url)?); // extract base path - the bit that comes after the scheme - let base_path = get_base_path(docker_url); + let base_path = get_base_path(docker_url)?; let mut configuration = Configuration::new(client); - configuration.base_path = base_path.to_string(); + configuration.base_path = base_path + .to_str() + .expect("URL points to a path that cannot be represented in UTF-8") + .to_string(); let scheme = docker_url.scheme().to_string(); configuration.uri_composer = Box::new(move |base_path, path| { @@ -96,10 +100,10 @@ impl DockerModuleRuntime { } } -fn get_base_path(url: &Url) -> &str { +fn get_base_path(url: &Url) -> Result { match url.scheme() { - "unix" => url.path(), - _ => url.as_str(), + "unix" => Ok(url.to_uds_file_path()?), + _ => Ok(url.as_str().into()), } } diff --git a/edgelet/edgelet-http-mgmt/src/client/module.rs b/edgelet/edgelet-http-mgmt/src/client/module.rs index c2affeba643..1fa646f3057 100644 --- a/edgelet/edgelet-http-mgmt/src/client/module.rs +++ b/edgelet/edgelet-http-mgmt/src/client/module.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. use std::fmt; +use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -8,7 +9,7 @@ use std::time::Duration; use edgelet_core::SystemInfo as CoreSystemInfo; use edgelet_core::*; use edgelet_docker::{self, DockerConfig}; -use edgelet_http::{UrlConnector, API_VERSION}; +use edgelet_http::{UrlConnector, UrlExt, API_VERSION}; use futures::future::{self, FutureResult}; use futures::prelude::*; use futures::stream; @@ -29,9 +30,9 @@ impl ModuleClient { pub fn new(url: &Url) -> Result { let client = Client::builder().build(UrlConnector::new(url)?); - let base_path = get_base_path(url); + let base_path = get_base_path(url)?; let mut configuration = Configuration::new(client); - configuration.base_path = base_path.to_string(); + configuration.base_path = base_path.to_str().ok_or(ErrorKind::Utf8)?.to_string(); let scheme = url.scheme().to_string(); configuration.uri_composer = Box::new(move |base_path, path| { @@ -45,10 +46,10 @@ impl ModuleClient { } } -fn get_base_path(url: &Url) -> &str { +fn get_base_path(url: &Url) -> Result { match url.scheme() { - "unix" => url.path(), - _ => url.as_str(), + "unix" => Ok(url.to_uds_file_path()?), + _ => Ok(url.as_str().into()), } } diff --git a/edgelet/edgelet-http-mgmt/src/error.rs b/edgelet/edgelet-http-mgmt/src/error.rs index 35f397d2aa4..344b86acb43 100644 --- a/edgelet/edgelet-http-mgmt/src/error.rs +++ b/edgelet/edgelet-http-mgmt/src/error.rs @@ -50,6 +50,8 @@ pub enum ErrorKind { NotModified, #[fail(display = "Parse error")] Parse, + #[fail(display = "UTF-8 encode/decode error")] + Utf8, } impl Fail for Error { diff --git a/edgelet/edgelet-http/Cargo.toml b/edgelet/edgelet-http/Cargo.toml index b0ba4190010..1a57ed91b29 100644 --- a/edgelet/edgelet-http/Cargo.toml +++ b/edgelet/edgelet-http/Cargo.toml @@ -19,7 +19,7 @@ percent-encoding = "1.0" regex = "0.2" serde = "1.0" serde_json = "1.0" -tokio = "0.1.8" +tokio = "0.1.11" typed-headers = "0.1" url = "1.7" @@ -36,16 +36,19 @@ tokio-uds = "0.2" [target.'cfg(windows)'.dependencies] hyper-named-pipe = { path = "../hyper-named-pipe" } +hyperlocal-windows = { git = "https://github.com/Azure/hyperlocal-windows" } +mio-uds-windows = { git = "https://github.com/Azure/mio-uds-windows.git" } tokio-named-pipe = { path = "../tokio-named-pipe" } +tokio-uds-windows = { git = "https://github.com/Azure/tokio-uds-windows.git" } +winapi = { version = "0.3.5", features = ["winsock2"] } [dev-dependencies] lazy_static = "1.0" +tempfile = "3" +tempdir = "0.3.7" edgelet-test-utils = { path = "../edgelet-test-utils" } -[target.'cfg(unix)'.dev-dependencies] -tempfile = "3" - [target.'cfg(windows)'.dev-dependencies] httparse = "1.2" rand = "0.4" diff --git a/edgelet/edgelet-http/src/lib.rs b/edgelet/edgelet-http/src/lib.rs index ab9e9346093..2973ad40471 100644 --- a/edgelet/edgelet-http/src/lib.rs +++ b/edgelet/edgelet-http/src/lib.rs @@ -28,11 +28,15 @@ extern crate hyper_proxy; extern crate hyper_tls; #[cfg(unix)] extern crate hyperlocal; +#[cfg(windows)] +extern crate hyperlocal_windows; #[cfg(target_os = "linux")] #[cfg(unix)] extern crate libc; #[macro_use] extern crate log; +#[cfg(windows)] +extern crate mio_uds_windows; #[cfg(unix)] extern crate nix; extern crate percent_encoding; @@ -44,7 +48,9 @@ extern crate serde; #[macro_use] extern crate serde_json; extern crate systemd; -#[cfg(unix)] +#[cfg(test)] +#[cfg(windows)] +extern crate tempdir; #[cfg(test)] extern crate tempfile; extern crate tokio; @@ -52,8 +58,12 @@ extern crate tokio; extern crate tokio_named_pipe; #[cfg(unix)] extern crate tokio_uds; +#[cfg(windows)] +extern crate tokio_uds_windows; extern crate typed_headers; extern crate url; +#[cfg(windows)] +extern crate winapi; #[macro_use] extern crate edgelet_utils; @@ -64,6 +74,9 @@ use std::net; use std::net::ToSocketAddrs; #[cfg(unix)] use std::os::unix::io::FromRawFd; +#[cfg(unix)] +use std::path::Path; +use std::path::PathBuf; use std::sync::Arc; use futures::{future, Future, Poll, Stream}; @@ -97,7 +110,6 @@ use self::util::incoming::Incoming; const HTTP_SCHEME: &str = "http"; const TCP_SCHEME: &str = "tcp"; -#[cfg(unix)] const UNIX_SCHEME: &str = "unix"; #[cfg(unix)] const FD_SCHEME: &str = "fd"; @@ -219,9 +231,8 @@ impl HyperExt for Http { let listener = TcpListener::bind(&addr)?; Incoming::Tcp(listener) } - #[cfg(unix)] UNIX_SCHEME => { - let path = url.path(); + let path = url.to_uds_file_path()?; unix::listener(path)? } #[cfg(unix)] @@ -257,3 +268,33 @@ impl HyperExt for Http { }) } } + +pub trait UrlExt { + fn to_uds_file_path(&self) -> Result; +} + +impl UrlExt for Url { + #[cfg(unix)] + fn to_uds_file_path(&self) -> Result { + debug_assert_eq!(self.scheme(), UNIX_SCHEME); + Ok(Path::new(self.path()).to_path_buf()) + } + + #[cfg(windows)] + fn to_uds_file_path(&self) -> Result { + // We get better handling of Windows file syntax if we parse a + // unix:// URL as a file:// URL. Specifically: + // - On Unix, `Url::parse("unix:///path")?.to_file_path()` succeeds and + // returns "/path". + // - On Windows, `Url::parse("unix:///C:/path")?.to_file_path()` fails + // with Err(()). + // - On Windows, `Url::parse("file:///C:/path")?.to_file_path()` succeeds + // and returns "C:\\path". + debug_assert_eq!(self.scheme(), UNIX_SCHEME); + let mut s = self.to_string(); + s.replace_range(..4, "file"); + let url = Url::parse(&s).map_err(|_| Error::from(ErrorKind::InvalidUri(s.clone())))?; + url.to_file_path() + .map_err(|()| ErrorKind::InvalidUri(s.clone()).into()) + } +} diff --git a/edgelet/edgelet-http/src/pid.rs b/edgelet/edgelet-http/src/pid.rs index 8cfdd32c1d2..576701b484b 100644 --- a/edgelet/edgelet-http/src/pid.rs +++ b/edgelet/edgelet-http/src/pid.rs @@ -1,9 +1,15 @@ // Copyright (c) Microsoft. All rights reserved. +use std::io; + use edgelet_core::pid::Pid; use futures::prelude::*; use hyper::service::Service; use hyper::{Body, Error as HyperError, Request}; +#[cfg(unix)] +use tokio_uds::UnixStream; +#[cfg(windows)] +use tokio_uds_windows::UnixStream; #[derive(Clone)] pub struct PidService { @@ -35,28 +41,31 @@ where } } +pub trait UnixStreamExt { + fn pid(&self) -> io::Result; +} + +impl UnixStreamExt for UnixStream { + fn pid(&self) -> io::Result { + get_pid(self) + } +} + #[cfg(unix)] -pub use self::impl_unix::UnixStreamExt; +use self::impl_unix::get_pid; #[cfg(unix)] mod impl_unix { use libc::{c_void, getsockopt, ucred, SOL_SOCKET, SO_PEERCRED}; use std::os::unix::io::AsRawFd; use std::{io, mem}; + #[cfg(unix)] use tokio_uds::UnixStream; + #[cfg(windows)] + use tokio_uds_windows::UnixStream; use super::*; - pub trait UnixStreamExt { - fn pid(&self) -> io::Result; - } - - impl UnixStreamExt for UnixStream { - fn pid(&self) -> io::Result { - get_pid(self) - } - } - pub fn get_pid(sock: &UnixStream) -> io::Result { let raw_fd = sock.as_raw_fd(); let mut ucred = ucred { @@ -89,3 +98,38 @@ mod impl_unix { } } } + +#[cfg(windows)] +use self::impl_windows::get_pid; + +#[cfg(windows)] +mod impl_windows { + use std::io; + use std::os::windows::io::AsRawSocket; + use winapi::ctypes::c_long; + use winapi::um::winsock2::{ioctlsocket, WSAGetLastError, SOCKET_ERROR}; + + use super::*; + + // SIO_AF_UNIX_GETPEERPID is defined in the Windows header afunix.h. + const SIO_AF_UNIX_GETPEERPID: c_long = 0x5800_0100; + + pub fn get_pid(sock: &UnixStream) -> io::Result { + let raw_socket = sock.as_raw_socket(); + let mut pid = 0_u32; + let ret = unsafe { + #[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))] + ioctlsocket( + raw_socket as _, + SIO_AF_UNIX_GETPEERPID, + &mut pid as *mut u32, + ) + }; + if ret == SOCKET_ERROR { + Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() })) + } else { + #[cfg_attr(feature = "cargo-clippy", allow(cast_possible_wrap))] + Ok(Pid::Value(pid as _)) + } + } +} diff --git a/edgelet/edgelet-http/src/unix.rs b/edgelet/edgelet-http/src/unix.rs index c3dff82238f..4716c7ae541 100644 --- a/edgelet/edgelet-http/src/unix.rs +++ b/edgelet/edgelet-http/src/unix.rs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. -#![cfg(unix)] - use std::fs; +#[cfg(unix)] use std::os::unix::fs::MetadataExt; use std::path::Path; +#[cfg(unix)] use nix::sys::stat::{umask, Mode}; +#[cfg(unix)] use tokio_uds::UnixListener; +#[cfg(windows)] +use tokio_uds_windows::UnixListener; use error::Error; use util::incoming::Incoming; @@ -26,16 +29,9 @@ pub fn listener>(path: P) -> Result { fs::remove_file(&path)?; debug!("unlinked {}", path.as_ref().display()); - let mode = Mode::from_bits_truncate(metadata.mode()); - let mut mask = Mode::all(); - mask.toggle(mode); - - debug!( - "settings permissions {:#o} for {}...", - mode, - path.as_ref().display() - ); - let prev = umask(mask); + #[cfg(unix)] + let prev = set_umask(&metadata, path.as_ref()); + #[cfg(unix)] defer! {{ umask(prev); }} debug!("binding {}...", path.as_ref().display()); @@ -51,7 +47,19 @@ pub fn listener>(path: P) -> Result { Ok(listener) } +#[cfg(unix)] +fn set_umask(metadata: &fs::Metadata, path: &Path) -> Mode { + let mode = Mode::from_bits_truncate(metadata.mode()); + let mut mask = Mode::all(); + mask.toggle(mode); + + debug!("settings permissions {:#o} for {}...", mode, path.display()); + + umask(mask) +} + #[cfg(test)] +#[cfg(unix)] mod tests { use super::*; diff --git a/edgelet/edgelet-http/src/util/connector.rs b/edgelet/edgelet-http/src/util/connector.rs index 420a3ffee69..a2f5110e5d2 100644 --- a/edgelet/edgelet-http/src/util/connector.rs +++ b/edgelet/edgelet-http/src/util/connector.rs @@ -14,7 +14,6 @@ //! HTTP and Unix sockets respectively. use std::io; -#[cfg(unix)] use std::path::Path; use futures::{future, Future}; @@ -25,12 +24,14 @@ use hyper::Uri; use hyper_named_pipe::{PipeConnector, Uri as PipeUri}; #[cfg(unix)] use hyperlocal::{UnixConnector, Uri as HyperlocalUri}; +#[cfg(windows)] +use hyperlocal_windows::{UnixConnector, Uri as HyperlocalUri}; use url::{ParseError, Url}; use error::{Error, ErrorKind}; use util::StreamSelector; +use UrlExt; -#[cfg(unix)] const UNIX_SCHEME: &str = "unix"; #[cfg(windows)] const PIPE_SCHEME: &str = "npipe"; @@ -40,19 +41,30 @@ pub enum UrlConnector { Http(HttpConnector), #[cfg(windows)] Pipe(PipeConnector), - #[cfg(unix)] Unix(UnixConnector), } +fn socket_file_exists(path: &Path) -> bool { + if cfg!(windows) { + use std::fs; + // Unix domain socket files in Windows are reparse points, so path.exists() + // (which calls fs::metadata(path)) won't work. Use fs::symlink_metadata() + // instead. + fs::symlink_metadata(path).is_ok() + } else { + path.exists() + } +} + impl UrlConnector { pub fn new(url: &Url) -> Result { match url.scheme() { #[cfg(windows)] PIPE_SCHEME => Ok(UrlConnector::Pipe(PipeConnector)), - #[cfg(unix)] UNIX_SCHEME => { - if Path::new(url.path()).exists() { + let file_path = url.to_uds_file_path()?; + if socket_file_exists(&file_path) { Ok(UrlConnector::Unix(UnixConnector::new())) } else { Err(ErrorKind::InvalidUri(url.to_string()))? @@ -73,7 +85,6 @@ impl UrlConnector { match scheme { #[cfg(windows)] PIPE_SCHEME => Ok(PipeUri::new(base_path, path)?.into()), - #[cfg(unix)] UNIX_SCHEME => Ok(HyperlocalUri::new(base_path, path).into()), HTTP_SCHEME => Ok(Url::parse(base_path) .and_then(|base| base.join(path)) @@ -96,7 +107,6 @@ impl Connect for UrlConnector { #[cfg(windows)] (UrlConnector::Pipe(_), PIPE_SCHEME) => (), - #[cfg(unix)] (UrlConnector::Unix(_), UNIX_SCHEME) => (), (_, scheme) => { @@ -121,7 +131,6 @@ impl Connect for UrlConnector { })) as Self::Future } - #[cfg(unix)] UrlConnector::Unix(connector) => { Box::new(connector.connect(dst).and_then(|(unix_stream, connected)| { Ok((StreamSelector::Unix(unix_stream), connected)) @@ -133,7 +142,6 @@ impl Connect for UrlConnector { #[cfg(test)] mod tests { - #[cfg(unix)] use tempfile::NamedTempFile; use url::Url; @@ -146,7 +154,6 @@ mod tests { UrlConnector::new(&Url::parse("foo:///this/is/not/valid").unwrap()).unwrap(); } - #[cfg(unix)] #[test] #[should_panic(expected = "Invalid uri")] fn invalid_uds_url() { @@ -154,13 +161,12 @@ mod tests { UrlConnector::new(&Url::parse("unix:///this/file/does/not/exist").unwrap()).unwrap(); } - #[cfg(unix)] #[test] fn create_uds_succeeds() { let file = NamedTempFile::new().unwrap(); - let file_path = file.path().to_str().unwrap(); - let _connector = - UrlConnector::new(&Url::parse(&format!("unix://{}", file_path)).unwrap()).unwrap(); + let mut url = Url::from_file_path(file.path()).unwrap(); + url.set_scheme("unix").unwrap(); + let _connector = UrlConnector::new(&url).unwrap(); } #[test] diff --git a/edgelet/edgelet-http/src/util/incoming.rs b/edgelet/edgelet-http/src/util/incoming.rs index 1640c936492..90b3e162b72 100644 --- a/edgelet/edgelet-http/src/util/incoming.rs +++ b/edgelet/edgelet-http/src/util/incoming.rs @@ -6,12 +6,13 @@ use futures::{Poll, Stream}; use tokio::net::TcpListener; #[cfg(unix)] use tokio_uds::UnixListener; +#[cfg(windows)] +use tokio_uds_windows::UnixListener; use util::{IncomingSocketAddr, StreamSelector}; pub enum Incoming { Tcp(TcpListener), - #[cfg(unix)] Unix(UnixListener), } @@ -33,7 +34,6 @@ impl Stream for Incoming { Some((StreamSelector::Tcp(stream), IncomingSocketAddr::Tcp(addr))) }) } - #[cfg(unix)] Incoming::Unix(ref mut listener) => { let accept = match listener.poll_accept() { Ok(accept) => accept, diff --git a/edgelet/edgelet-http/src/util/mod.rs b/edgelet/edgelet-http/src/util/mod.rs index 345a5e04849..5a3f81e57c6 100644 --- a/edgelet/edgelet-http/src/util/mod.rs +++ b/edgelet/edgelet-http/src/util/mod.rs @@ -9,14 +9,17 @@ use std::os::unix::net::SocketAddr as UnixSocketAddr; use bytes::{Buf, BufMut}; use edgelet_core::pid::Pid; use futures::Poll; +#[cfg(windows)] +use mio_uds_windows::net::SocketAddr as UnixSocketAddr; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::TcpStream; #[cfg(windows)] use tokio_named_pipe::PipeStream; #[cfg(unix)] use tokio_uds::UnixStream; +#[cfg(windows)] +use tokio_uds_windows::UnixStream; -#[cfg(unix)] use pid::UnixStreamExt; pub mod connector; @@ -31,7 +34,6 @@ pub enum StreamSelector { Tcp(TcpStream), #[cfg(windows)] Pipe(PipeStream), - #[cfg(unix)] Unix(UnixStream), } @@ -42,7 +44,6 @@ impl StreamSelector { StreamSelector::Tcp(_) => Ok(Pid::Any), #[cfg(windows)] StreamSelector::Pipe(_) => Ok(Pid::Any), - #[cfg(unix)] StreamSelector::Unix(ref stream) => stream.pid(), } } @@ -54,7 +55,6 @@ impl Read for StreamSelector { StreamSelector::Tcp(ref mut stream) => stream.read(buf), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => stream.read(buf), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => stream.read(buf), } } @@ -66,7 +66,6 @@ impl Write for StreamSelector { StreamSelector::Tcp(ref mut stream) => stream.write(buf), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => stream.write(buf), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => stream.write(buf), } } @@ -76,7 +75,6 @@ impl Write for StreamSelector { StreamSelector::Tcp(ref mut stream) => stream.flush(), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => stream.flush(), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => stream.flush(), } } @@ -89,7 +87,6 @@ impl AsyncRead for StreamSelector { StreamSelector::Tcp(ref stream) => stream.prepare_uninitialized_buffer(buf), #[cfg(windows)] StreamSelector::Pipe(ref stream) => stream.prepare_uninitialized_buffer(buf), - #[cfg(unix)] StreamSelector::Unix(ref stream) => stream.prepare_uninitialized_buffer(buf), } } @@ -100,7 +97,6 @@ impl AsyncRead for StreamSelector { StreamSelector::Tcp(ref mut stream) => stream.read_buf(buf), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => stream.read_buf(buf), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => stream.read_buf(buf), } } @@ -112,7 +108,6 @@ impl AsyncWrite for StreamSelector { StreamSelector::Tcp(ref mut stream) => <&TcpStream>::shutdown(&mut &*stream), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => PipeStream::shutdown(stream), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => <&UnixStream>::shutdown(&mut &*stream), } } @@ -123,7 +118,6 @@ impl AsyncWrite for StreamSelector { StreamSelector::Tcp(ref mut stream) => stream.write_buf(buf), #[cfg(windows)] StreamSelector::Pipe(ref mut stream) => stream.write_buf(buf), - #[cfg(unix)] StreamSelector::Unix(ref mut stream) => stream.write_buf(buf), } } @@ -131,7 +125,6 @@ impl AsyncWrite for StreamSelector { pub enum IncomingSocketAddr { Tcp(SocketAddr), - #[cfg(unix)] Unix(UnixSocketAddr), } @@ -139,7 +132,6 @@ impl fmt::Display for IncomingSocketAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { IncomingSocketAddr::Tcp(ref socket) => socket.fmt(f), - #[cfg(unix)] IncomingSocketAddr::Unix(ref socket) => { if let Some(path) = socket.as_pathname() { write!(f, "{}", path.display()) @@ -151,21 +143,76 @@ impl fmt::Display for IncomingSocketAddr { } } -#[cfg(unix)] #[cfg(test)] mod tests { + #[cfg(windows)] + use tempdir::TempDir; + #[cfg(windows)] + use tokio::runtime::current_thread::Runtime; + #[cfg(unix)] + use tokio_uds::UnixStream; + #[cfg(windows)] + use tokio_uds_windows::{UnixListener, UnixStream}; + use super::*; + struct Pair { + #[cfg(windows)] + _dir: TempDir, + #[cfg(windows)] + _rt: Runtime, + + a: UnixStream, + b: UnixStream, + } + + #[cfg(unix)] + fn socket_pair() -> Pair { + let (a, b) = UnixStream::pair().unwrap(); + Pair { a, b } + } + + #[cfg(windows)] + fn socket_pair() -> Pair { + // 'pair' not implemented on Windows + use futures::sync::oneshot; + use futures::{Future, Stream}; + let dir = TempDir::new("uds").unwrap(); + let addr = dir.path().join("sock"); + let mut rt = Runtime::new().unwrap(); + let server = UnixListener::bind(&addr).unwrap(); + let (tx, rx) = oneshot::channel(); + rt.spawn( + server + .incoming() + .into_future() + .and_then(move |(sock, _)| { + tx.send(sock.unwrap()).unwrap(); + Ok(()) + }).map_err(|e| panic!("err={:?}", e)), + ); + + let a = rt.block_on(UnixStream::connect(&addr)).unwrap(); + let b = rt.block_on(rx).unwrap(); + Pair { + _dir: dir, + _rt: rt, + a, + b, + } + } + #[test] + #[cfg_attr(windows, ignore)] // TODO: remove when windows build servers are upgraded to RS5 fn test_pid() { - let (a, b) = UnixStream::pair().unwrap(); - assert_eq!(a.pid().unwrap(), b.pid().unwrap()); - match a.pid().unwrap() { + let pair = socket_pair(); + assert_eq!(pair.a.pid().unwrap(), pair.b.pid().unwrap()); + match pair.a.pid().unwrap() { Pid::None => panic!("no pid 'a'"), Pid::Any => panic!("any pid 'a'"), Pid::Value(_) => (), } - match b.pid().unwrap() { + match pair.b.pid().unwrap() { Pid::None => panic!("no pid 'b'"), Pid::Any => panic!("any pid 'b'"), Pid::Value(_) => (), diff --git a/edgelet/edgelet-http/tests/connector.rs b/edgelet/edgelet-http/tests/connector.rs index 1888f885c31..200eaeaddc3 100644 --- a/edgelet/edgelet-http/tests/connector.rs +++ b/edgelet/edgelet-http/tests/connector.rs @@ -19,15 +19,14 @@ extern crate hyper_named_pipe; #[cfg(unix)] extern crate hyperlocal; #[cfg(windows)] +extern crate hyperlocal_windows; +#[cfg(windows)] extern crate rand; -#[cfg(unix)] -#[macro_use(defer)] -extern crate scopeguard; +extern crate tempdir; extern crate tokio; extern crate typed_headers; extern crate url; -#[cfg(unix)] use std::io; #[cfg(windows)] use std::sync::mpsc::channel; @@ -37,7 +36,6 @@ use std::thread; use edgelet_http::UrlConnector; #[cfg(windows)] use edgelet_test_utils::run_pipe_server; -#[cfg(unix)] use edgelet_test_utils::run_uds_server; use edgelet_test_utils::{get_unused_tcp_port, run_tcp_server}; use futures::future; @@ -52,7 +50,10 @@ use hyper_named_pipe::Uri as PipeUri; #[cfg(unix)] use hyperlocal::Uri as HyperlocalUri; #[cfg(windows)] +use hyperlocal_windows::Uri as HyperlocalUri; +#[cfg(windows)] use rand::Rng; +use tempdir::TempDir; use typed_headers::mime; use typed_headers::{ContentLength, ContentType, HeaderMapExt}; use url::Url; @@ -91,23 +92,20 @@ fn tcp_get() { runtime.block_on(task).unwrap(); } -#[cfg(unix)] #[test] +#[cfg_attr(windows, ignore)] // TODO: remove when windows build servers are upgraded to RS5 fn uds_get() { - let file_path = "/tmp/edgelet_test_uds_get.sock"; - - // make sure file gets deleted when test is done - defer! {{ - ::std::fs::remove_file(&file_path).unwrap_or(()); - }} + let dir = TempDir::new("uds").unwrap(); + let file_path = dir.path().join("sock"); + let file_path = file_path.to_str().unwrap(); - let path_copy = file_path.to_string(); - let server = run_uds_server(&path_copy, |req| { + let server = run_uds_server(&file_path, |req| { hello_handler(req).map_err(|err| io::Error::new(io::ErrorKind::Other, err)) }).map_err(|err| eprintln!("{}", err)); - let connector = - UrlConnector::new(&Url::parse(&format!("unix://{}", file_path)).unwrap()).unwrap(); + let mut url = Url::from_file_path(file_path).unwrap(); + url.set_scheme("unix").unwrap(); + let connector = UrlConnector::new(&url).unwrap(); let client = Client::builder().build::<_, Body>(connector); let task = client @@ -228,23 +226,20 @@ fn tcp_post() { runtime.block_on(task).unwrap(); } -#[cfg(unix)] #[test] +#[cfg_attr(windows, ignore)] // TODO: remove when windows build servers are upgraded to RS5 fn uds_post() { - let file_path = "/tmp/edgelet_test_uds_post.sock"; - - // make sure file gets deleted when test is done - defer! {{ - ::std::fs::remove_file(&file_path).unwrap_or(()); - }} + let dir = TempDir::new("uds").unwrap(); + let file_path = dir.path().join("sock"); + let file_path = file_path.to_str().unwrap(); - let path_copy = file_path.to_string(); - let server = run_uds_server(&path_copy, |req| { + let server = run_uds_server(&file_path, |req| { hello_handler(req).map_err(|err| io::Error::new(io::ErrorKind::Other, err)) }).map_err(|err| eprintln!("{}", err)); - let connector = - UrlConnector::new(&Url::parse(&format!("unix://{}", file_path)).unwrap()).unwrap(); + let mut url = Url::from_file_path(file_path).unwrap(); + url.set_scheme("unix").unwrap(); + let connector = UrlConnector::new(&url).unwrap(); let client = Client::builder().build::<_, Body>(connector); diff --git a/edgelet/edgelet-test-utils/Cargo.toml b/edgelet/edgelet-test-utils/Cargo.toml index d304ef484f4..a9912c78083 100644 --- a/edgelet/edgelet-test-utils/Cargo.toml +++ b/edgelet/edgelet-test-utils/Cargo.toml @@ -21,5 +21,7 @@ hyperlocal = "0.6" [target.'cfg(windows)'.dependencies] httparse = "1.2" +hyperlocal-windows = { git = "https://github.com/Azure/hyperlocal-windows" } mio = "0.6" mio-named-pipes = "0.1" +mio-uds-windows = { git = "https://github.com/Azure/mio-uds-windows.git" } diff --git a/edgelet/edgelet-test-utils/src/lib.rs b/edgelet/edgelet-test-utils/src/lib.rs index b281a2732c7..9020fefd0a1 100644 --- a/edgelet/edgelet-test-utils/src/lib.rs +++ b/edgelet/edgelet-test-utils/src/lib.rs @@ -23,9 +23,13 @@ extern crate hyper; #[cfg(unix)] extern crate hyperlocal; #[cfg(windows)] +extern crate hyperlocal_windows; +#[cfg(windows)] extern crate mio; #[cfg(windows)] extern crate mio_named_pipes; +#[cfg(windows)] +extern crate mio_uds_windows; extern crate serde; #[macro_use] extern crate serde_derive; @@ -43,7 +47,6 @@ pub mod web; pub use json_connector::{JsonConnector, StaticStream}; pub use web::run_tcp_server; -#[cfg(unix)] pub use web::run_uds_server; #[cfg(windows)] diff --git a/edgelet/edgelet-test-utils/src/web/linux.rs b/edgelet/edgelet-test-utils/src/web/linux.rs deleted file mode 100644 index 612103b35d5..00000000000 --- a/edgelet/edgelet-test-utils/src/web/linux.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -use std::fs; -use std::io; -use std::os::unix::net::UnixListener as StdUnixListener; - -use futures::prelude::*; -use hyper::service::service_fn; -use hyper::{self, Body, Request, Response}; -use hyperlocal::server::{Http as UdsHttp, Incoming as UdsIncoming}; - -pub fn run_uds_server(path: &str, handler: F) -> impl Future -where - F: 'static + Fn(Request) -> R + Clone + Send + Sync, - R: 'static + Future, Error = io::Error> + Send, -{ - fs::remove_file(&path).unwrap_or(()); - - // Bind a listener synchronously, so that the caller's client will not fail to connect - // regardless of when the asynchronous server accepts the connection - let listener = StdUnixListener::bind(path).unwrap(); - let incoming = UdsIncoming::from_std(listener, &Default::default()).unwrap(); - let serve = UdsHttp::new().serve_incoming(incoming, move || service_fn(handler.clone())); - - serve.for_each(|connecting| { - connecting - .then(|connection| { - let connection = connection.unwrap(); - Ok::<_, hyper::Error>(connection) - }).flatten() - .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("failed to serve connection: {}", e), - ) - }) - }) -} diff --git a/edgelet/edgelet-test-utils/src/web/mod.rs b/edgelet/edgelet-test-utils/src/web/mod.rs index 985f36385dd..56214dc29a8 100644 --- a/edgelet/edgelet-test-utils/src/web/mod.rs +++ b/edgelet/edgelet-test-utils/src/web/mod.rs @@ -1,21 +1,26 @@ // Copyright (c) Microsoft. All rights reserved. -#[cfg(unix)] -mod linux; - #[cfg(windows)] mod windows; -#[cfg(unix)] -pub use self::linux::run_uds_server; - #[cfg(windows)] pub use self::windows::run_pipe_server; +use std::fs; +use std::io; +#[cfg(unix)] +use std::os::unix::net::UnixListener as StdUnixListener; + use futures::prelude::*; use hyper::server::conn::Http; use hyper::service::service_fn; use hyper::{self, Body, Request, Response}; +#[cfg(unix)] +use hyperlocal::server::{Http as UdsHttp, Incoming as UdsIncoming}; +#[cfg(windows)] +use hyperlocal_windows::server::{Http as UdsHttp, Incoming as UdsIncoming}; +#[cfg(windows)] +use mio_uds_windows::net::UnixListener as StdUnixListener; pub fn run_tcp_server( ip: &str, @@ -39,3 +44,31 @@ where }).flatten() }) } + +pub fn run_uds_server(path: &str, handler: F) -> impl Future +where + F: 'static + Fn(Request) -> R + Clone + Send + Sync, + R: 'static + Future, Error = io::Error> + Send, +{ + fs::remove_file(&path).unwrap_or(()); + + // Bind a listener synchronously, so that the caller's client will not fail to connect + // regardless of when the asynchronous server accepts the connection + let listener = StdUnixListener::bind(path).unwrap(); + let incoming = UdsIncoming::from_std(listener, &Default::default()).unwrap(); + let serve = UdsHttp::new().serve_incoming(incoming, move || service_fn(handler.clone())); + + serve.for_each(|connecting| { + connecting + .then(|connection| { + let connection = connection.unwrap(); + Ok::<_, hyper::Error>(connection) + }).flatten() + .map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("failed to serve connection: {}", e), + ) + }) + }) +} diff --git a/edgelet/iotedged/src/config/windows/default.yaml b/edgelet/iotedged/src/config/windows/default.yaml index b267f049bd9..05858d558ed 100644 --- a/edgelet/iotedged/src/config/windows/default.yaml +++ b/edgelet/iotedged/src/config/windows/default.yaml @@ -13,12 +13,12 @@ agent: hostname: "localhost" connect: - management_uri: "http://localhost:15580" - workload_uri: "http://localhost:15581" + management_uri: "unix:///C:/ProgramData/iotedge/mgmt/sock" + workload_uri: "unix:///C:/ProgramData/iotedge/workload/sock" listen: - management_uri: "http://0.0.0.0:15580" - workload_uri: "http://0.0.0.0:15581" + management_uri: "unix:///C:/ProgramData/iotedge/mgmt/sock" + workload_uri: "unix:///C:/ProgramData/iotedge/workload/sock" homedir: "C:\\ProgramData\\iotedge" diff --git a/edgelet/iotedged/src/error.rs b/edgelet/iotedged/src/error.rs index 8c17856e5eb..2bfcdbb5682 100644 --- a/edgelet/iotedged/src/error.rs +++ b/edgelet/iotedged/src/error.rs @@ -31,7 +31,7 @@ pub struct Error { inner: Context, } -#[derive(Clone, Copy, Debug, Fail, PartialEq)] +#[derive(Clone, Debug, Fail, PartialEq)] pub enum ErrorKind { #[fail(display = "Invalid configuration file")] Settings, @@ -80,6 +80,8 @@ pub enum ErrorKind { #[cfg(target_os = "windows")] #[fail(display = "Windows service error")] WindowsService, + #[fail(display = "Invalid uri {}", _0)] + InvalidUri(String), } impl Error { diff --git a/edgelet/iotedged/src/lib.rs b/edgelet/iotedged/src/lib.rs index 866ad7ec8c5..561d576d7ac 100644 --- a/edgelet/iotedged/src/lib.rs +++ b/edgelet/iotedged/src/lib.rs @@ -92,7 +92,7 @@ use edgelet_hsm::tpm::{TpmKey, TpmKeyStore}; use edgelet_hsm::Crypto; use edgelet_http::client::{Client as HttpClient, ClientImpl}; use edgelet_http::logging::LoggingService; -use edgelet_http::{ApiVersionService, HyperExt, MaybeProxyClient, API_VERSION}; +use edgelet_http::{ApiVersionService, HyperExt, MaybeProxyClient, UrlExt, API_VERSION}; use edgelet_http_mgmt::ManagementService; use edgelet_http_workload::WorkloadService; use edgelet_iothub::{HubIdentityManager, SasTokenSource}; @@ -682,7 +682,21 @@ fn vol_mount_uri(config: &mut DockerConfig, uris: &[&Url]) -> Result<(), Error> // if the url is a domain socket URL then vol mount it into the container for uri in uris { if uri.scheme() == UNIX_SCHEME { - binds.push(format!("{}:{}", uri.path(), uri.path())); + let path = uri.to_uds_file_path()?; + // On Windows we mount the parent folder because we can't mount the + // socket files directly + #[cfg(windows)] + let path = path + .parent() + .ok_or_else(|| ErrorKind::InvalidUri(uri.to_string()))?; + let path = path + .to_str() + .expect("URL points to a path that cannot be represented in UTF-8") + .to_string(); + let bind = format!("{}:{}", &path, &path); + if !binds.contains(&bind) { + binds.push(bind); + } } } diff --git a/scripts/windows/setup/IotEdgeSecurityDaemon.ps1 b/scripts/windows/setup/IotEdgeSecurityDaemon.ps1 index 380e2eed203..2791ec84a81 100644 --- a/scripts/windows/setup/IotEdgeSecurityDaemon.ps1 +++ b/scripts/windows/setup/IotEdgeSecurityDaemon.ps1 @@ -49,6 +49,10 @@ function Install-SecurityDaemon { $ErrorActionPreference = "Stop" Set-StrictMode -Version 5 + Set-Variable Windows1607 -Value 14393 -Option Constant + Set-Variable Windows1803 -Value 17134 -Option Constant + Set-Variable Windows1809 -Value 17763 -Option Constant + if (Test-EdgeAlreadyInstalled) { Write-Host ("`nIoT Edge is already installed. To reinstall, run 'Uninstall-SecurityDaemon' first.") ` -ForegroundColor "Red" @@ -87,8 +91,10 @@ function Install-SecurityDaemon { Set-ProvisioningMode Set-AgentImage Set-Hostname - Set-GatewayAddress Set-MobyNetwork + if (-not (Test-UdsSupport)) { + Set-GatewayAddress + } Install-IotEdgeService Write-Host ("`nThis device is now provisioned with the IoT Edge runtime.`n" + @@ -173,18 +179,28 @@ function Test-IsDockerRunning { return $true } +function Get-WindowsBuild { + return (Get-Item "HKLM:\Software\Microsoft\Windows NT\CurrentVersion").GetValue("CurrentBuild") +} + +function Test-UdsSupport { + # TODO: Enable when we have RS5-based modules in process-isolated containers + # $MinBuildForUnixDomainSockets = $Windows1809 + # $CurrentBuild = Get-WindowsBuild + # return ($ContainerOs -eq "Windows" -and $CurrentBuild -ge $MinBuildForUnixDomainSockets) + return $false +} + function Test-IsKernelValid { - $MinBuildForLinuxContainers = 14393 - $SupportedBuildsForWindowsContainers = @(17134, 17763) - $CurrentBuild = (Get-Item "HKLM:\Software\Microsoft\Windows NT\CurrentVersion").GetValue("CurrentBuild") + $MinBuildForLinuxContainers = $Windows1607 + $SupportedBuildsForWindowsContainers = @($Windows1803, $Windows1809) + $CurrentBuild = Get-WindowsBuild - # If using Linux containers, any Windows 10 version >14393 will suffice. if (($ContainerOs -eq "Linux" -and $CurrentBuild -ge $MinBuildForLinuxContainers) -or ` ($ContainerOs -eq "Windows" -and $SupportedBuildsForWindowsContainers -contains $CurrentBuild)) { Write-Host "The container host is on supported build version $CurrentBuild." -ForegroundColor "Green" return $true } else { - Write-Host ("The container host is on unsupported build version $CurrentBuild. `n" + "Please use a container host running build $MinBuildForLinuxContainers newer when using Linux containers" + "or with one of the following supported build versions when using Windows containers:`n" + @@ -255,6 +271,23 @@ function Get-SecurityDaemon { Copy-Item "C:\ProgramData\iotedge\iotedged-windows\*" "C:\ProgramData\iotedge" -Force -Recurse } + if (Test-UdsSupport) { + foreach ($Name in "mgmt", "workload") + { + # We can't bind socket files directly in Windows, so create a folder + # and bind to that. The folder needs to give Modify rights to a + # well-known group that will exist in any container so that + # non-privileged modules can access it. + $Path = "C:\ProgramData\iotedge\$Name" + New-Item "$Path" -ItemType "Directory" -Force + $Rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule(` + "NT AUTHORITY\Authenticated Users", 'Modify', 'ObjectInherit', 'InheritOnly', 'Allow') + $Acl = [System.IO.Directory]::GetAccessControl($Path) + $Acl.AddAccessRule($Rule) + [System.IO.Directory]::SetAccessControl($Path, $Acl) + } + } + if (Test-Path 'C:\ProgramData\iotedge\iotedged_eventlog_messages.dll') { # This release uses iotedged_eventlog_messages.dll as the eventlog message file