mirror of https://github.com/dswd/vpncloud.git
Client works
This commit is contained in:
parent
edd0e7a29f
commit
d50490ac51
|
@ -31,18 +31,39 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base-x"
|
name = "base-x"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boxfnonce"
|
name = "boxfnonce"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -106,6 +127,28 @@ version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
|
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpuid-bool"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "daemonize"
|
name = "daemonize"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -116,6 +159,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "discard"
|
name = "discard"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -134,6 +186,21 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -144,6 +211,16 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.15"
|
version = "0.1.15"
|
||||||
|
@ -195,6 +272,12 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -219,6 +302,15 @@ dependencies = [
|
||||||
"xmltree",
|
"xmltree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "input_buffer"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -267,6 +359,24 @@ version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
@ -298,12 +408,57 @@ version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d008f51b1acffa0d3450a68606e6a51c123012edaacb0f4e1426bd978869187"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"foreign-types",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
@ -494,6 +649,39 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -549,6 +737,19 @@ dependencies = [
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha-1"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"cpuid-bool",
|
||||||
|
"digest",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -772,6 +973,32 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"input_buffer",
|
||||||
|
"log",
|
||||||
|
"native-tls",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"sha-1",
|
||||||
|
"url",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -826,6 +1053,18 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -866,6 +1105,8 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
"tungstenite",
|
||||||
|
"url",
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ privdrop = "0.5"
|
||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
smallvec = "1.5"
|
smallvec = "1.5"
|
||||||
|
tungstenite = "*"
|
||||||
|
url = "*"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
|
@ -98,11 +98,7 @@ pub struct GenericCloud<D: Device, P: Protocol, S: Socket, TS: TimeSource> {
|
||||||
|
|
||||||
impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
|
impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(config: &Config, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
|
pub fn new(config: &Config, socket: S, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
|
||||||
let socket = match S::listen(config.listen) {
|
|
||||||
Ok(socket) => socket,
|
|
||||||
Err(err) => fail!("Failed to open socket {}: {}", config.listen, err)
|
|
||||||
};
|
|
||||||
let (learning, broadcast) = match config.mode {
|
let (learning, broadcast) = match config.mode {
|
||||||
Mode::Normal => {
|
Mode::Normal => {
|
||||||
match config.device_type {
|
match config.device_type {
|
||||||
|
|
290
src/config.rs
290
src/config.rs
|
@ -5,29 +5,12 @@
|
||||||
use super::{device::Type, types::Mode, util::Duration};
|
use super::{device::Type, types::Mode, util::Duration};
|
||||||
pub use crate::crypto::Config as CryptoConfig;
|
pub use crate::crypto::Config as CryptoConfig;
|
||||||
|
|
||||||
use std::{
|
use std::cmp::max;
|
||||||
cmp::max,
|
|
||||||
net::{IpAddr, Ipv6Addr, SocketAddr}
|
|
||||||
};
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
|
||||||
pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
|
pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
|
||||||
pub const DEFAULT_PORT: u16 = 3210;
|
pub const DEFAULT_PORT: u16 = 3210;
|
||||||
|
|
||||||
|
|
||||||
fn parse_listen(addr: &str) -> SocketAddr {
|
|
||||||
if let Some(addr) = addr.strip_prefix("*:") {
|
|
||||||
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
|
|
||||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
|
||||||
} else if addr.contains(':') {
|
|
||||||
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
|
|
||||||
} else {
|
|
||||||
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
|
|
||||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, PartialEq, Clone)]
|
#[derive(Deserialize, Debug, PartialEq, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub device_type: Type,
|
pub device_type: Type,
|
||||||
|
@ -41,7 +24,7 @@ pub struct Config {
|
||||||
|
|
||||||
pub crypto: CryptoConfig,
|
pub crypto: CryptoConfig,
|
||||||
|
|
||||||
pub listen: SocketAddr,
|
pub listen: String,
|
||||||
pub peers: Vec<String>,
|
pub peers: Vec<String>,
|
||||||
pub peer_timeout: Duration,
|
pub peer_timeout: Duration,
|
||||||
pub keepalive: Option<Duration>,
|
pub keepalive: Option<Duration>,
|
||||||
|
@ -60,7 +43,7 @@ pub struct Config {
|
||||||
pub statsd_server: Option<String>,
|
pub statsd_server: Option<String>,
|
||||||
pub statsd_prefix: Option<String>,
|
pub statsd_prefix: Option<String>,
|
||||||
pub user: Option<String>,
|
pub user: Option<String>,
|
||||||
pub group: Option<String>
|
pub group: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -74,7 +57,7 @@ impl Default for Config {
|
||||||
ifup: None,
|
ifup: None,
|
||||||
ifdown: None,
|
ifdown: None,
|
||||||
crypto: CryptoConfig::default(),
|
crypto: CryptoConfig::default(),
|
||||||
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
|
listen: "[::]:3210".to_string(),
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
|
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
|
||||||
keepalive: None,
|
keepalive: None,
|
||||||
|
@ -93,7 +76,7 @@ impl Default for Config {
|
||||||
statsd_server: None,
|
statsd_server: None,
|
||||||
statsd_prefix: None,
|
statsd_prefix: None,
|
||||||
user: None,
|
user: None,
|
||||||
group: None
|
group: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +108,7 @@ impl Config {
|
||||||
self.ifdown = Some(val);
|
self.ifdown = Some(val);
|
||||||
}
|
}
|
||||||
if let Some(val) = file.listen {
|
if let Some(val) = file.listen {
|
||||||
self.listen = parse_listen(&val);
|
self.listen = val;
|
||||||
}
|
}
|
||||||
if let Some(mut val) = file.peers {
|
if let Some(mut val) = file.peers {
|
||||||
self.peers.append(&mut val);
|
self.peers.append(&mut val);
|
||||||
|
@ -223,7 +206,7 @@ impl Config {
|
||||||
self.ifdown = Some(val);
|
self.ifdown = Some(val);
|
||||||
}
|
}
|
||||||
if let Some(val) = args.listen {
|
if let Some(val) = args.listen {
|
||||||
self.listen = parse_listen(&val);
|
self.listen = val;
|
||||||
}
|
}
|
||||||
self.peers.append(&mut args.peers);
|
self.peers.append(&mut args.peers);
|
||||||
if let Some(val) = args.peer_timeout {
|
if let Some(val) = args.peer_timeout {
|
||||||
|
@ -296,12 +279,11 @@ impl Config {
|
||||||
pub fn get_keepalive(&self) -> Duration {
|
pub fn get_keepalive(&self) -> Duration {
|
||||||
match self.keepalive {
|
match self.keepalive {
|
||||||
Some(dur) => dur,
|
Some(dur) => dur,
|
||||||
None => max(self.peer_timeout / 2 - 60, 1)
|
None => max(self.peer_timeout / 2 - 60, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Default)]
|
#[derive(StructOpt, Debug, Default)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Read configuration options from the specified file.
|
/// Read configuration options from the specified file.
|
||||||
|
@ -325,7 +307,7 @@ pub struct Args {
|
||||||
pub mode: Option<Mode>,
|
pub mode: Option<Mode>,
|
||||||
|
|
||||||
/// The shared password to encrypt all traffic
|
/// The shared password to encrypt all traffic
|
||||||
#[structopt(short, long, required_unless_one = &["private-key", "config", "genkey", "version"], env)]
|
#[structopt(short, long, env, global = true)]
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
|
||||||
/// The private key to use
|
/// The private key to use
|
||||||
|
@ -416,10 +398,6 @@ pub struct Args {
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub version: bool,
|
pub version: bool,
|
||||||
|
|
||||||
/// Generate and print a key-pair and exit
|
|
||||||
#[structopt(long, conflicts_with = "private_key")]
|
|
||||||
pub genkey: bool,
|
|
||||||
|
|
||||||
/// Disable automatic port forwarding
|
/// Disable automatic port forwarding
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub no_port_forwarding: bool,
|
pub no_port_forwarding: bool,
|
||||||
|
@ -456,9 +434,28 @@ pub struct Args {
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub log_file: Option<String>,
|
pub log_file: Option<String>,
|
||||||
|
|
||||||
/// Migrate an old config file
|
#[structopt(subcommand)]
|
||||||
#[structopt(long, alias = "migrate", requires = "config")]
|
pub cmd: Option<Command>,
|
||||||
pub migrate_config: bool
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Generate and print a key-pair and exit
|
||||||
|
#[structopt(name = "genkey", alias = "gen-key")]
|
||||||
|
GenKey,
|
||||||
|
|
||||||
|
WsProxy,
|
||||||
|
|
||||||
|
WsClient {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[structopt(alias = "migrate")]
|
||||||
|
MigrateConfig {
|
||||||
|
/// Config file
|
||||||
|
#[structopt(long)]
|
||||||
|
config_file: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
|
@ -468,7 +465,7 @@ pub struct ConfigFileDevice {
|
||||||
pub type_: Option<Type>,
|
pub type_: Option<Type>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub path: Option<String>,
|
pub path: Option<String>,
|
||||||
pub fix_rp_filter: Option<bool>
|
pub fix_rp_filter: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
|
@ -477,14 +474,14 @@ pub struct ConfigFileBeacon {
|
||||||
pub store: Option<String>,
|
pub store: Option<String>,
|
||||||
pub load: Option<String>,
|
pub load: Option<String>,
|
||||||
pub interval: Option<Duration>,
|
pub interval: Option<Duration>,
|
||||||
pub password: Option<String>
|
pub password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||||
pub struct ConfigFileStatsd {
|
pub struct ConfigFileStatsd {
|
||||||
pub server: Option<String>,
|
pub server: Option<String>,
|
||||||
pub prefix: Option<String>
|
pub prefix: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
|
@ -512,10 +509,9 @@ pub struct ConfigFile {
|
||||||
pub stats_file: Option<String>,
|
pub stats_file: Option<String>,
|
||||||
pub statsd: Option<ConfigFileStatsd>,
|
pub statsd: Option<ConfigFileStatsd>,
|
||||||
pub user: Option<String>,
|
pub user: Option<String>,
|
||||||
pub group: Option<String>
|
pub group: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_file() {
|
fn config_file() {
|
||||||
let config_file = "
|
let config_file = "
|
||||||
|
@ -549,41 +545,44 @@ statsd:
|
||||||
server: example.com:1234
|
server: example.com:1234
|
||||||
prefix: prefix
|
prefix: prefix
|
||||||
";
|
";
|
||||||
assert_eq!(serde_yaml::from_str::<ConfigFile>(config_file).unwrap(), ConfigFile {
|
assert_eq!(
|
||||||
device: Some(ConfigFileDevice {
|
serde_yaml::from_str::<ConfigFile>(config_file).unwrap(),
|
||||||
type_: Some(Type::Tun),
|
ConfigFile {
|
||||||
name: Some("vpncloud%d".to_string()),
|
device: Some(ConfigFileDevice {
|
||||||
path: Some("/dev/net/tun".to_string()),
|
type_: Some(Type::Tun),
|
||||||
fix_rp_filter: None
|
name: Some("vpncloud%d".to_string()),
|
||||||
}),
|
path: Some("/dev/net/tun".to_string()),
|
||||||
ip: Some("10.0.1.1/16".to_string()),
|
fix_rp_filter: None
|
||||||
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
}),
|
||||||
ifdown: Some("true".to_string()),
|
ip: Some("10.0.1.1/16".to_string()),
|
||||||
crypto: CryptoConfig::default(),
|
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
||||||
listen: None,
|
ifdown: Some("true".to_string()),
|
||||||
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
|
crypto: CryptoConfig::default(),
|
||||||
peer_timeout: Some(600),
|
listen: None,
|
||||||
keepalive: Some(840),
|
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
|
||||||
beacon: Some(ConfigFileBeacon {
|
peer_timeout: Some(600),
|
||||||
store: Some("/run/vpncloud.beacon.out".to_string()),
|
keepalive: Some(840),
|
||||||
load: Some("/run/vpncloud.beacon.in".to_string()),
|
beacon: Some(ConfigFileBeacon {
|
||||||
interval: Some(3600),
|
store: Some("/run/vpncloud.beacon.out".to_string()),
|
||||||
password: Some("test123".to_string())
|
load: Some("/run/vpncloud.beacon.in".to_string()),
|
||||||
}),
|
interval: Some(3600),
|
||||||
mode: Some(Mode::Normal),
|
password: Some("test123".to_string())
|
||||||
switch_timeout: Some(300),
|
}),
|
||||||
claims: Some(vec!["10.0.1.0/24".to_string()]),
|
mode: Some(Mode::Normal),
|
||||||
auto_claim: None,
|
switch_timeout: Some(300),
|
||||||
port_forwarding: Some(true),
|
claims: Some(vec!["10.0.1.0/24".to_string()]),
|
||||||
user: Some("nobody".to_string()),
|
auto_claim: None,
|
||||||
group: Some("nogroup".to_string()),
|
port_forwarding: Some(true),
|
||||||
pid_file: Some("/run/vpncloud.run".to_string()),
|
user: Some("nobody".to_string()),
|
||||||
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
group: Some("nogroup".to_string()),
|
||||||
statsd: Some(ConfigFileStatsd {
|
pid_file: Some("/run/vpncloud.run".to_string()),
|
||||||
server: Some("example.com:1234".to_string()),
|
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
||||||
prefix: Some("prefix".to_string())
|
statsd: Some(ConfigFileStatsd {
|
||||||
})
|
server: Some("example.com:1234".to_string()),
|
||||||
})
|
prefix: Some("prefix".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -597,7 +596,7 @@ fn default_config_as_default() {
|
||||||
ifup: None,
|
ifup: None,
|
||||||
ifdown: None,
|
ifdown: None,
|
||||||
crypto: CryptoConfig::default(),
|
crypto: CryptoConfig::default(),
|
||||||
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
|
listen: "[::]:3210".to_string(),
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
peer_timeout: 0,
|
peer_timeout: 0,
|
||||||
keepalive: None,
|
keepalive: None,
|
||||||
|
@ -616,9 +615,10 @@ fn default_config_as_default() {
|
||||||
statsd_server: None,
|
statsd_server: None,
|
||||||
statsd_prefix: None,
|
statsd_prefix: None,
|
||||||
user: None,
|
user: None,
|
||||||
group: None
|
group: None,
|
||||||
};
|
};
|
||||||
let default_config_file = serde_yaml::from_str::<ConfigFile>(include_str!("../assets/example.net.disabled")).unwrap();
|
let default_config_file =
|
||||||
|
serde_yaml::from_str::<ConfigFile>(include_str!("../assets/example.net.disabled")).unwrap();
|
||||||
default_config.merge_file(default_config_file);
|
default_config.merge_file(default_config_file);
|
||||||
assert_eq!(default_config, Config::default());
|
assert_eq!(default_config, Config::default());
|
||||||
}
|
}
|
||||||
|
@ -631,7 +631,7 @@ fn config_merge() {
|
||||||
type_: Some(Type::Tun),
|
type_: Some(Type::Tun),
|
||||||
name: Some("vpncloud%d".to_string()),
|
name: Some("vpncloud%d".to_string()),
|
||||||
path: None,
|
path: None,
|
||||||
fix_rp_filter: None
|
fix_rp_filter: None,
|
||||||
}),
|
}),
|
||||||
ip: None,
|
ip: None,
|
||||||
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
||||||
|
@ -645,7 +645,7 @@ fn config_merge() {
|
||||||
store: Some("/run/vpncloud.beacon.out".to_string()),
|
store: Some("/run/vpncloud.beacon.out".to_string()),
|
||||||
load: Some("/run/vpncloud.beacon.in".to_string()),
|
load: Some("/run/vpncloud.beacon.in".to_string()),
|
||||||
interval: Some(7200),
|
interval: Some(7200),
|
||||||
password: Some("test123".to_string())
|
password: Some("test123".to_string()),
|
||||||
}),
|
}),
|
||||||
mode: Some(Mode::Normal),
|
mode: Some(Mode::Normal),
|
||||||
switch_timeout: Some(300),
|
switch_timeout: Some(300),
|
||||||
|
@ -658,36 +658,39 @@ fn config_merge() {
|
||||||
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
||||||
statsd: Some(ConfigFileStatsd {
|
statsd: Some(ConfigFileStatsd {
|
||||||
server: Some("example.com:1234".to_string()),
|
server: Some("example.com:1234".to_string()),
|
||||||
prefix: Some("prefix".to_string())
|
prefix: Some("prefix".to_string()),
|
||||||
})
|
}),
|
||||||
});
|
|
||||||
assert_eq!(config, Config {
|
|
||||||
device_type: Type::Tun,
|
|
||||||
device_name: "vpncloud%d".to_string(),
|
|
||||||
device_path: None,
|
|
||||||
ip: None,
|
|
||||||
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
|
||||||
ifdown: Some("true".to_string()),
|
|
||||||
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
|
|
||||||
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
|
|
||||||
peer_timeout: 600,
|
|
||||||
keepalive: Some(840),
|
|
||||||
switch_timeout: 300,
|
|
||||||
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
|
|
||||||
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
|
|
||||||
beacon_interval: 7200,
|
|
||||||
beacon_password: Some("test123".to_string()),
|
|
||||||
mode: Mode::Normal,
|
|
||||||
port_forwarding: true,
|
|
||||||
claims: vec!["10.0.1.0/24".to_string()],
|
|
||||||
user: Some("nobody".to_string()),
|
|
||||||
group: Some("nogroup".to_string()),
|
|
||||||
pid_file: Some("/run/vpncloud.run".to_string()),
|
|
||||||
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
|
||||||
statsd_server: Some("example.com:1234".to_string()),
|
|
||||||
statsd_prefix: Some("prefix".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
});
|
});
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
Config {
|
||||||
|
device_type: Type::Tun,
|
||||||
|
device_name: "vpncloud%d".to_string(),
|
||||||
|
device_path: None,
|
||||||
|
ip: None,
|
||||||
|
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
||||||
|
ifdown: Some("true".to_string()),
|
||||||
|
listen: "[::]:3210".to_string(),
|
||||||
|
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
|
||||||
|
peer_timeout: 600,
|
||||||
|
keepalive: Some(840),
|
||||||
|
switch_timeout: 300,
|
||||||
|
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
|
||||||
|
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
|
||||||
|
beacon_interval: 7200,
|
||||||
|
beacon_password: Some("test123".to_string()),
|
||||||
|
mode: Mode::Normal,
|
||||||
|
port_forwarding: true,
|
||||||
|
claims: vec!["10.0.1.0/24".to_string()],
|
||||||
|
user: Some("nobody".to_string()),
|
||||||
|
group: Some("nogroup".to_string()),
|
||||||
|
pid_file: Some("/run/vpncloud.run".to_string()),
|
||||||
|
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
||||||
|
statsd_server: Some("example.com:1234".to_string()),
|
||||||
|
statsd_prefix: Some("prefix".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
config.merge_args(Args {
|
config.merge_args(Args {
|
||||||
type_: Some(Type::Tap),
|
type_: Some(Type::Tap),
|
||||||
device: Some("vpncloud0".to_string()),
|
device: Some("vpncloud0".to_string()),
|
||||||
|
@ -716,38 +719,41 @@ fn config_merge() {
|
||||||
group: Some("root".to_string()),
|
group: Some("root".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
assert_eq!(config, Config {
|
assert_eq!(
|
||||||
device_type: Type::Tap,
|
config,
|
||||||
device_name: "vpncloud0".to_string(),
|
Config {
|
||||||
device_path: Some("/dev/null".to_string()),
|
device_type: Type::Tap,
|
||||||
fix_rp_filter: false,
|
device_name: "vpncloud0".to_string(),
|
||||||
ip: None,
|
device_path: Some("/dev/null".to_string()),
|
||||||
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
|
fix_rp_filter: false,
|
||||||
ifdown: Some("ifconfig $IFNAME down".to_string()),
|
ip: None,
|
||||||
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
|
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
|
||||||
listen: "[::]:3211".parse::<SocketAddr>().unwrap(),
|
ifdown: Some("ifconfig $IFNAME down".to_string()),
|
||||||
peers: vec![
|
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
|
||||||
"remote.machine.foo:3210".to_string(),
|
listen: "[::]:3211".to_string(),
|
||||||
"remote.machine.bar:3210".to_string(),
|
peers: vec![
|
||||||
"another:3210".to_string()
|
"remote.machine.foo:3210".to_string(),
|
||||||
],
|
"remote.machine.bar:3210".to_string(),
|
||||||
peer_timeout: 1801,
|
"another:3210".to_string()
|
||||||
keepalive: Some(850),
|
],
|
||||||
switch_timeout: 301,
|
peer_timeout: 1801,
|
||||||
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
|
keepalive: Some(850),
|
||||||
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
|
switch_timeout: 301,
|
||||||
beacon_interval: 3600,
|
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
|
||||||
beacon_password: Some("test1234".to_string()),
|
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
|
||||||
mode: Mode::Switch,
|
beacon_interval: 3600,
|
||||||
port_forwarding: false,
|
beacon_password: Some("test1234".to_string()),
|
||||||
claims: vec!["10.0.1.0/24".to_string()],
|
mode: Mode::Switch,
|
||||||
auto_claim: true,
|
port_forwarding: false,
|
||||||
user: Some("root".to_string()),
|
claims: vec!["10.0.1.0/24".to_string()],
|
||||||
group: Some("root".to_string()),
|
auto_claim: true,
|
||||||
pid_file: Some("/run/vpncloud-mynet.run".to_string()),
|
user: Some("root".to_string()),
|
||||||
stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()),
|
group: Some("root".to_string()),
|
||||||
statsd_server: Some("example.com:2345".to_string()),
|
pid_file: Some("/run/vpncloud-mynet.run".to_string()),
|
||||||
statsd_prefix: Some("prefix2".to_string()),
|
stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()),
|
||||||
daemonize: true
|
statsd_server: Some("example.com:2345".to_string()),
|
||||||
});
|
statsd_prefix: Some("prefix2".to_string()),
|
||||||
|
daemonize: true
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
126
src/main.rs
126
src/main.rs
|
@ -4,11 +4,15 @@
|
||||||
|
|
||||||
#![cfg_attr(feature = "bench", feature(test))]
|
#![cfg_attr(feature = "bench", feature(test))]
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
#[macro_use] extern crate serde_derive;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
#[cfg(test)] extern crate tempfile;
|
#[cfg(test)]
|
||||||
#[cfg(feature = "bench")] extern crate test;
|
extern crate tempfile;
|
||||||
|
#[cfg(feature = "bench")]
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -30,6 +34,7 @@ pub mod port_forwarding;
|
||||||
pub mod table;
|
pub mod table;
|
||||||
pub mod traffic;
|
pub mod traffic;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod wsproxy;
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
@ -39,26 +44,26 @@ use std::{
|
||||||
net::{Ipv4Addr, UdpSocket},
|
net::{Ipv4Addr, UdpSocket},
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::Path,
|
path::Path,
|
||||||
process::Command,
|
process,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
thread
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cloud::GenericCloud,
|
cloud::GenericCloud,
|
||||||
config::{Args, Config},
|
config::{Args, Command, Config},
|
||||||
crypto::Crypto,
|
crypto::Crypto,
|
||||||
device::{Device, TunTapDevice, Type},
|
device::{Device, TunTapDevice, Type},
|
||||||
|
net::Socket,
|
||||||
oldconfig::OldConfigFile,
|
oldconfig::OldConfigFile,
|
||||||
payload::Protocol,
|
payload::Protocol,
|
||||||
port_forwarding::PortForwarding,
|
util::SystemTimeSource,
|
||||||
util::SystemTimeSource
|
wsproxy::ProxyConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct DualLogger {
|
struct DualLogger {
|
||||||
file: Option<Mutex<File>>
|
file: Option<Mutex<File>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DualLogger {
|
impl DualLogger {
|
||||||
|
@ -105,7 +110,7 @@ impl log::Log for DualLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_script(script: &str, ifname: &str) {
|
fn run_script(script: &str, ifname: &str) {
|
||||||
let mut cmd = Command::new("sh");
|
let mut cmd = process::Command::new("sh");
|
||||||
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
|
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
|
||||||
debug!("Running script: {:?}", cmd);
|
debug!("Running script: {:?}", cmd);
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
|
@ -114,18 +119,18 @@ fn run_script(script: &str, ifname: &str) {
|
||||||
error!("Script returned with error: {:?}", status.code())
|
error!("Script returned with error: {:?}", status.code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to execute script {:?}: {}", script, e)
|
Err(e) => error!("Failed to execute script {:?}: {}", script, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
|
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
|
||||||
let (ip_str, len_str) = match addr.find('/') {
|
let (ip_str, len_str) = match addr.find('/') {
|
||||||
Some(pos) => (&addr[..pos], &addr[pos + 1..]),
|
Some(pos) => (&addr[..pos], &addr[pos + 1..]),
|
||||||
None => (addr, "24")
|
None => (addr, "24"),
|
||||||
};
|
};
|
||||||
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
|
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
|
||||||
if prefix_len > 32 {
|
if prefix_len > 32 {
|
||||||
return Err(format!("Invalid prefix length: {}", prefix_len))
|
return Err(format!("Invalid prefix length: {}", prefix_len));
|
||||||
}
|
}
|
||||||
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
|
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
|
||||||
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
|
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
|
||||||
|
@ -162,11 +167,10 @@ fn setup_device(config: &Config) -> TunTapDevice {
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn run<P: Protocol>(config: Config) {
|
fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
||||||
let device = setup_device(&config);
|
let device = setup_device(&config);
|
||||||
let port_forwarding = if config.port_forwarding { PortForwarding::new(config.listen.port()) } else { None };
|
let port_forwarding = if config.port_forwarding { socket.create_port_forwarding() } else { None };
|
||||||
let stats_file = match config.stats_file {
|
let stats_file = match config.stats_file {
|
||||||
None => None,
|
None => None,
|
||||||
Some(ref name) => {
|
Some(ref name) => {
|
||||||
|
@ -183,7 +187,7 @@ fn run<P: Protocol>(config: Config) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut cloud =
|
let mut cloud =
|
||||||
GenericCloud::<TunTapDevice, P, UdpSocket, SystemTimeSource>::new(&config, device, port_forwarding, stats_file);
|
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file);
|
||||||
for addr in config.peers {
|
for addr in config.peers {
|
||||||
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
|
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
|
||||||
cloud.add_reconnect_peer(addr);
|
cloud.add_reconnect_peer(addr);
|
||||||
|
@ -224,15 +228,7 @@ fn main() {
|
||||||
let args: Args = Args::from_args();
|
let args: Args = Args::from_args();
|
||||||
if args.version {
|
if args.version {
|
||||||
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
|
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
|
||||||
return
|
return;
|
||||||
}
|
|
||||||
if args.genkey {
|
|
||||||
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref());
|
|
||||||
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
|
|
||||||
println!(
|
|
||||||
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
|
|
||||||
);
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
|
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
|
||||||
log::set_boxed_logger(Box::new(logger)).unwrap();
|
log::set_boxed_logger(Box::new(logger)).unwrap();
|
||||||
|
@ -244,24 +240,43 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
log::LevelFilter::Info
|
log::LevelFilter::Info
|
||||||
});
|
});
|
||||||
if args.migrate_config {
|
if let Some(cmd) = args.cmd {
|
||||||
let file = args.config.unwrap();
|
match cmd {
|
||||||
info!("Trying to convert from old config format");
|
Command::GenKey => {
|
||||||
let f = try_fail!(File::open(&file), "Failed to open config file: {:?}");
|
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref());
|
||||||
let config_file_old: OldConfigFile =
|
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
|
||||||
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
|
println!(
|
||||||
let new_config = config_file_old.convert();
|
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
|
||||||
info!("Successfully converted from old format");
|
);
|
||||||
info!("Renaming original file to {}.orig", file);
|
}
|
||||||
try_fail!(fs::rename(&file, format!("{}.orig", file)), "Failed to rename original file: {:?}");
|
Command::MigrateConfig { config_file } => {
|
||||||
info!("Writing new config back into {}", file);
|
info!("Trying to convert from old config format");
|
||||||
let f = try_fail!(File::create(&file), "Failed to open config file: {:?}");
|
let f = try_fail!(File::open(&config_file), "Failed to open config file: {:?}");
|
||||||
try_fail!(
|
let config_file_old: OldConfigFile =
|
||||||
fs::set_permissions(&file, fs::Permissions::from_mode(0o600)),
|
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
|
||||||
"Failed to set permissions on file: {:?}"
|
let new_config = config_file_old.convert();
|
||||||
);
|
info!("Successfully converted from old format");
|
||||||
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
|
info!("Renaming original file to {}.orig", config_file);
|
||||||
return
|
try_fail!(
|
||||||
|
fs::rename(&config_file, format!("{}.orig", config_file)),
|
||||||
|
"Failed to rename original file: {:?}"
|
||||||
|
);
|
||||||
|
info!("Writing new config back into {}", config_file);
|
||||||
|
let f = try_fail!(File::create(&config_file), "Failed to open config file: {:?}");
|
||||||
|
try_fail!(
|
||||||
|
fs::set_permissions(&config_file, fs::Permissions::from_mode(0o600)),
|
||||||
|
"Failed to set permissions on file: {:?}"
|
||||||
|
);
|
||||||
|
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
|
||||||
|
}
|
||||||
|
Command::WsProxy => {
|
||||||
|
wsproxy::run_proxy();
|
||||||
|
}
|
||||||
|
Command::WsClient { url } => {
|
||||||
|
wsproxy::run_client(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
if let Some(ref file) = args.config {
|
if let Some(ref file) = args.config {
|
||||||
|
@ -284,8 +299,21 @@ fn main() {
|
||||||
}
|
}
|
||||||
config.merge_args(args);
|
config.merge_args(args);
|
||||||
debug!("Config: {:?}", config);
|
debug!("Config: {:?}", config);
|
||||||
match config.device_type {
|
if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
|
||||||
Type::Tap => run::<payload::Frame>(config),
|
error!("Either password or private key must be set in config or given as parameter");
|
||||||
Type::Tun => run::<payload::Packet>(config)
|
return;
|
||||||
|
}
|
||||||
|
if config.listen.starts_with("ws") {
|
||||||
|
let socket = try_fail!(ProxyConnection::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||||
|
match config.device_type {
|
||||||
|
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||||
|
Type::Tun => run::<payload::Packet, _>(config, socket),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||||
|
match config.device_type {
|
||||||
|
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||||
|
Type::Tun => run::<payload::Packet, _>(config, socket),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/net.rs
33
src/net.rs
|
@ -5,12 +5,13 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
io::{self, ErrorKind},
|
io::{self, ErrorKind},
|
||||||
net::{IpAddr, SocketAddr, UdpSocket},
|
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr},
|
||||||
os::unix::io::{AsRawFd, RawFd},
|
os::unix::io::{AsRawFd, RawFd},
|
||||||
sync::atomic::{AtomicBool, Ordering}
|
sync::atomic::{AtomicBool, Ordering}
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
|
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
|
||||||
|
use crate::port_forwarding::PortForwarding;
|
||||||
|
|
||||||
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
|
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
|
||||||
match addr {
|
match addr {
|
||||||
|
@ -21,14 +22,28 @@ pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
|
||||||
|
|
||||||
|
|
||||||
pub trait Socket: AsRawFd + Sized {
|
pub trait Socket: AsRawFd + Sized {
|
||||||
fn listen(addr: SocketAddr) -> Result<Self, io::Error>;
|
fn listen(addr: &str) -> Result<Self, io::Error>;
|
||||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error>;
|
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error>;
|
||||||
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
|
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
|
||||||
fn address(&self) -> Result<SocketAddr, io::Error>;
|
fn address(&self) -> Result<SocketAddr, io::Error>;
|
||||||
|
fn create_port_forwarding(&self) -> Option<PortForwarding>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_listen(addr: &str) -> SocketAddr {
|
||||||
|
if let Some(addr) = addr.strip_prefix("*:") {
|
||||||
|
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
|
||||||
|
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
||||||
|
} else if addr.contains(':') {
|
||||||
|
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
|
||||||
|
} else {
|
||||||
|
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
|
||||||
|
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Socket for UdpSocket {
|
impl Socket for UdpSocket {
|
||||||
fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
|
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||||
|
let addr = parse_listen(addr);
|
||||||
UdpSocket::bind(addr)
|
UdpSocket::bind(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +61,10 @@ impl Socket for UdpSocket {
|
||||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||||
self.local_addr()
|
self.local_addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||||
|
PortForwarding::new(self.address().unwrap().port())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
@ -106,8 +125,8 @@ impl AsRawFd for MockSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Socket for MockSocket {
|
impl Socket for MockSocket {
|
||||||
fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
|
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||||
Ok(Self::new(addr))
|
Ok(Self::new(parse_listen(addr)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||||
|
@ -132,6 +151,10 @@ impl Socket for MockSocket {
|
||||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||||
Ok(self.address)
|
Ok(self.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
use super::{
|
||||||
|
net::{mapped_addr, Socket},
|
||||||
|
port_forwarding::PortForwarding,
|
||||||
|
util::MsgBuffer
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
io::{self, Write, Read, Cursor},
|
||||||
|
net::{SocketAddr, TcpListener, SocketAddrV6, Ipv6Addr},
|
||||||
|
os::unix::io::{AsRawFd, RawFd},
|
||||||
|
thread::spawn,
|
||||||
|
};
|
||||||
|
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, stream::Stream, Message};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub fn run_proxy() {
|
||||||
|
let server = TcpListener::bind("127.0.0.1:9001").unwrap();
|
||||||
|
for stream in server.incoming() {
|
||||||
|
info!("connect");
|
||||||
|
spawn(move || {
|
||||||
|
let mut websocket = accept(stream.unwrap()).unwrap();
|
||||||
|
loop {
|
||||||
|
let msg = websocket.read_message().unwrap();
|
||||||
|
// We do not want to send back ping/pong messages.
|
||||||
|
if msg.is_binary() || msg.is_text() {
|
||||||
|
info!("msg");
|
||||||
|
websocket.write_message(msg).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProxyConnection {
|
||||||
|
url: String,
|
||||||
|
addr: SocketAddr,
|
||||||
|
socket: WebSocket<AutoStream>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for ProxyConnection {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
match self.socket.get_ref() {
|
||||||
|
Stream::Plain(sock) => sock.as_raw_fd(),
|
||||||
|
Stream::Tls(sock) => sock.get_ref().as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Socket for ProxyConnection {
|
||||||
|
fn listen(url: &str) -> Result<Self, io::Error> {
|
||||||
|
let (mut socket, _) = connect(Url::parse(url).unwrap()).unwrap();
|
||||||
|
let addr = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
|
||||||
|
Ok(ProxyConnection { url: addr.to_string(), addr, socket })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||||
|
buffer.clear();
|
||||||
|
match self.socket.read_message().unwrap() {
|
||||||
|
Message::Binary(data) => {
|
||||||
|
let mut cursor = Cursor::new(&data);
|
||||||
|
let mut ip = [0u8; 16];
|
||||||
|
cursor.read_exact(&mut ip)?;
|
||||||
|
let port = cursor.read_u16::<NetworkEndian>()?;
|
||||||
|
let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0));
|
||||||
|
buffer.clone_from(&data[18..]);
|
||||||
|
Ok(addr)
|
||||||
|
},
|
||||||
|
_ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error> {
|
||||||
|
let mut msg = Vec::with_capacity(data.len() + 18);
|
||||||
|
let addr = mapped_addr(addr);
|
||||||
|
match mapped_addr(addr) {
|
||||||
|
SocketAddr::V6(addr) => {
|
||||||
|
msg.write_all(&addr.ip().octets())?;
|
||||||
|
msg.write_u16::<NetworkEndian>(addr.port())?;
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
msg.write_all(data)?;
|
||||||
|
self.socket.write_message(Message::Binary(msg)).unwrap();
|
||||||
|
Ok(data.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||||
|
Ok(self.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_client(url: String) {
|
||||||
|
let (mut socket, _) = connect(Url::parse(&url).unwrap()).unwrap();
|
||||||
|
socket.write_message(Message::Text("test".to_string())).unwrap();
|
||||||
|
let msg = socket.read_message().unwrap();
|
||||||
|
info!("msg: {}", msg);
|
||||||
|
socket.close(None).unwrap();
|
||||||
|
}
|
Loading…
Reference in New Issue