diff --git a/Cargo.lock b/Cargo.lock index 2fd545f865e..f4e875719fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -356,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "regex-automata 0.3.4", + "regex-automata 0.3.6", "serde", ] @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", "libc", @@ -552,9 +552,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", @@ -573,9 +573,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", @@ -908,9 +908,9 @@ dependencies = [ [[package]] name = "critical-section" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "crossbeam-channel" @@ -1153,6 +1153,12 @@ dependencies = [ "parking_lot_core 0.9.8", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "deranged" version = "0.3.7" @@ -1350,6 +1356,18 @@ dependencies = [ "memmap2 0.5.10", ] +[[package]] +name = "educe" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "079044df30bb07de7d846d41a184c4b00e66ebdac93ee459253474f3a47e50ae" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.9.0" @@ -1397,6 +1415,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "enumset" version = "1.1.2" @@ -1494,13 +1525,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -1828,7 +1859,7 @@ dependencies = [ "indexmap 1.9.3", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.8", "tracing", ] @@ -2000,7 +2031,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2034,6 +2065,19 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tungstenite" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226df6fd0aece319a325419d770aa9d947defa60463f142cd82b329121f906a3" +dependencies = [ + "hyper", + "pin-project", + "tokio", + "tokio-tungstenite", + "tungstenite", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -2265,9 +2309,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -2686,6 +2730,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -2788,9 +2853,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -2820,9 +2885,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -2975,18 +3040,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", @@ -2995,9 +3060,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -3341,13 +3406,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.4", + "regex-automata 0.3.6", "regex-syntax 0.7.4", ] @@ -3362,9 +3427,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -3445,7 +3510,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util", + "tokio-util 0.7.8", "tower-service", "url", "wasm-bindgen", @@ -3500,6 +3565,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rpassword" version = "7.2.0" @@ -3573,9 +3660,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.6" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", @@ -3607,9 +3694,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.2" +version = "0.101.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" dependencies = [ "ring", "untrusted", @@ -3824,9 +3911,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -3863,9 +3950,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -3972,6 +4059,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.7" @@ -4081,6 +4179,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -4184,9 +4292,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -4207,9 +4315,9 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", @@ -4394,18 +4502,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.29.1" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys 0.48.0", ] @@ -4441,6 +4548,50 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" +dependencies = [ + "bincode", + "bytes", + "educe", + "futures-core", + "futures-sink", + "pin-project", + "rmp-serde", + "serde", + "serde_cbor", + "serde_json", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -4609,6 +4760,29 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tracing-test" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a2c0ff408fe918a94c428a3f2ad04e4afd5c95bbc08fcf868eff750c15728a4" +dependencies = [ + "lazy_static", + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08" +dependencies = [ + "lazy_static", + "quote", + "syn 1.0.109", +] + [[package]] name = "tracing-wasm" version = "0.2.1" @@ -4641,6 +4815,25 @@ dependencies = [ "termcolor", ] +[[package]] +name = "tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.16.0" @@ -4770,6 +4963,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8parse" version = "0.2.1" @@ -4844,8 +5043,10 @@ dependencies = [ "async-trait", "bytes", "derivative", + "futures", "mio", - "socket2", + "serde", + "socket2 0.4.9", "thiserror", "tracing", ] @@ -4854,15 +5055,27 @@ dependencies = [ name = "virtual-net" version = "0.4.0" dependencies = [ + "anyhow", "async-trait", + "base64", + "bincode", "bytes", "derivative", + "futures-util", + "hyper", + "hyper-tungstenite", "libc", "mio", - "socket2", + "pin-project-lite", + "serde", + "socket2 0.4.9", "thiserror", "tokio", + "tokio-serde", + "tokio-tungstenite", + "tokio-util 0.6.10", "tracing", + "tracing-test", "virtual-mio", ] @@ -5087,9 +5300,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -5128,9 +5341,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -5142,9 +5355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote", @@ -5152,9 +5365,9 @@ dependencies = [ [[package]] name = "wasm-coredump-builder" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a45b77df22075a4cc28a1f1b0ffde2f98ea9427b08be653cd42301cc31a733" +checksum = "31ca262b320e4530a60946ba16a1cbf987d3f7d4aa6a953bfcc96e179e3e7458" dependencies = [ "wasm-coredump-encoder", "wasm-coredump-types", @@ -5163,9 +5376,9 @@ dependencies = [ [[package]] name = "wasm-coredump-encoder" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c768410ee257f529009c138d91ba69e5b03c406954401411ca2ce7e172a3043" +checksum = "f6f36ccfe604720ce093fce7d7b0d609c086c646ec4bb9bba58cb9f4dc2c5623" dependencies = [ "leb128", "wasm-coredump-types", @@ -5173,9 +5386,9 @@ dependencies = [ [[package]] name = "wasm-coredump-types" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c10cfb746471f9d2f70beac757f29919baae2b666dbc52c3f8f8d0c9d32afe" +checksum = "d2763d9807903c461b41275a13489396d04695d7bc365743b8ea430cfd72f336" [[package]] name = "wasm-encoder" @@ -5577,7 +5790,7 @@ dependencies = [ "wasmer-api", "wasmer-deploy-schema", "wasmer-deploy-util", - "wasmer-registry 5.3.0", + "wasmer-registry 5.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-toml 0.6.0", "webc", ] @@ -5720,11 +5933,10 @@ dependencies = [ [[package]] name = "wasmer-registry" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c6b4b47cf736e1c6aaf27f2b12a4140d5399c9609e08815cdc4cb622f0c726" +version = "5.4.0" dependencies = [ "anyhow", + "clap", "console", "dirs", "filetime", @@ -5738,6 +5950,7 @@ dependencies = [ "log", "lzma-rs", "minisign", + "pretty_assertions", "regex", "reqwest", "rpassword", @@ -5754,7 +5967,7 @@ dependencies = [ "toml 0.5.11", "url", "wasmer-toml 0.6.0", - "wasmer-wasm-interface 4.1.0", + "wasmer-wasm-interface 4.1.1", "wasmparser 0.51.4", "whoami", ] @@ -5762,9 +5975,10 @@ dependencies = [ [[package]] name = "wasmer-registry" version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d843168f099071b078c39f89b34030cc31876f300889fe932c4c8baff06c14d6" dependencies = [ "anyhow", - "clap", "console", "dirs", "filetime", @@ -5778,7 +5992,6 @@ dependencies = [ "log", "lzma-rs", "minisign", - "pretty_assertions", "regex", "reqwest", "rpassword", @@ -5795,7 +6008,7 @@ dependencies = [ "toml 0.5.11", "url", "wasmer-toml 0.6.0", - "wasmer-wasm-interface 4.1.1", + "wasmer-wasm-interface 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.51.4", "whoami", ] @@ -6000,26 +6213,26 @@ dependencies = [ [[package]] name = "wasmer-wasm-interface" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "676a4e64975caa2cc3947fe061ac4f7fc2efaa1ed8c192120b46d79419f8419e" +version = "4.1.1" dependencies = [ + "bincode", "either", "nom 5.1.3", "serde", "wasmparser 0.51.4", + "wat", ] [[package]] name = "wasmer-wasm-interface" version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88903f75b88a541cc4d2f38c38c9cf2786262c14fa2cf2002808c92f9f84049f" dependencies = [ - "bincode", "either", "nom 5.1.3", "serde", "wasmparser 0.51.4", - "wat", ] [[package]] @@ -6254,9 +6467,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -6546,9 +6759,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] @@ -6584,9 +6797,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 7ed4dfabe34..751f7a2f8df 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -108,6 +108,9 @@ impl Args { Some(Cmd::Init(init)) => init.execute(), Some(Cmd::Login(login)) => login.execute(), Some(Cmd::Publish(publish)) => publish.execute(), + /* + Some(Cmd::Connect(connect)) => connect.execute(), + */ #[cfg(feature = "static-artifact-create")] Some(Cmd::GenCHeader(gen_heder)) => gen_heder.execute(), #[cfg(feature = "wast")] diff --git a/lib/virtual-io/Cargo.toml b/lib/virtual-io/Cargo.toml index 5fd25d17e84..5f3c87d78ca 100644 --- a/lib/virtual-io/Cargo.toml +++ b/lib/virtual-io/Cargo.toml @@ -17,6 +17,8 @@ tracing = "0.1" mio = { version = "0.8", features = [ "os-poll" ], optional = true } socket2 = { version = "0.4", optional = true } derivative = { version = "^2" } +futures = { version = "0.3" } +serde = { version = "1.0", default-features = false, features = ["derive"] } [features] sys = [ "mio", "socket2" ] \ No newline at end of file diff --git a/lib/virtual-io/src/interest.rs b/lib/virtual-io/src/interest.rs index bff6cb0bbbe..8da03be08b9 100644 --- a/lib/virtual-io/src/interest.rs +++ b/lib/virtual-io/src/interest.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex}, @@ -6,7 +7,7 @@ use std::{ use derivative::Derivative; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InterestType { Readable, Writable, diff --git a/lib/virtual-io/src/lib.rs b/lib/virtual-io/src/lib.rs index e1f82d10c79..f5fa03bbc8e 100644 --- a/lib/virtual-io/src/lib.rs +++ b/lib/virtual-io/src/lib.rs @@ -3,9 +3,11 @@ mod guard; mod interest; #[cfg(feature = "sys")] mod selector; +pub mod waker; #[cfg(feature = "sys")] pub use guard::*; pub use interest::*; #[cfg(feature = "sys")] pub use selector::*; +pub use waker::*; diff --git a/lib/wasix/src/runtime/task_manager/waker.rs b/lib/virtual-io/src/waker.rs similarity index 96% rename from lib/wasix/src/runtime/task_manager/waker.rs rename to lib/virtual-io/src/waker.rs index 124c14afbda..f9fe822635d 100644 --- a/lib/wasix/src/runtime/task_manager/waker.rs +++ b/lib/virtual-io/src/waker.rs @@ -11,7 +11,7 @@ pub struct InlineWaker { condvar: Condvar, } impl InlineWaker { - fn new() -> Arc { + pub fn new() -> Arc { Arc::new(Self { lock: Mutex::new(()), condvar: Condvar::new(), @@ -28,7 +28,7 @@ impl InlineWaker { self.condvar.notify_all(); } - fn as_waker(self: &Arc) -> Waker { + pub fn as_waker(self: &Arc) -> Waker { let s: *const Self = Arc::into_raw(Arc::clone(self)); let raw_waker = RawWaker::new(s as *const (), &VTABLE); unsafe { Waker::from_raw(raw_waker) } diff --git a/lib/virtual-net/Cargo.toml b/lib/virtual-net/Cargo.toml index a090ffa19db..9a5c017d1ce 100644 --- a/lib/virtual-net/Cargo.toml +++ b/lib/virtual-net/Cargo.toml @@ -14,16 +14,38 @@ thiserror = "1" bytes = "1.1" async-trait = { version = "^0.1" } tracing = "0.1" -tokio = { version = "1", features = [ "net", "rt" ], default_features = false, optional = true } +tokio = { version = "1", default_features = false, optional = true } libc = { version = "0.2.139", optional = true } mio = { version = "0.8", optional = true } socket2 = { version = "0.4", optional = true } derivative = { version = "^2" } virtual-mio = { path = "../virtual-io", version = "0.1.0", default-features = false } +base64 = "0.21" +bincode = { version = "1.3" } +serde = { version = "1.0", default-features = false, features = ["derive"] } +pin-project-lite = "0.2.9" +futures-util = { version = "0.3" } +anyhow = "1.0" +tokio-serde = { version = "0.8", features = [ "bincode" ], optional = true } +tokio-util = { version = "0.6", features = ["codec"], optional = true } +hyper-tungstenite = { version = "0.10", optional = true } +hyper = { version = "0.14", optional = true } +tokio-tungstenite = { version = "0.19", optional = true } + +[dev-dependencies] +tokio = { version = "1", default_features = false, features = [ "macros" ] } +tracing-test = { version = "0.2" } [features] -host-net = [ "tokio", "libc", "virtual-mio/sys", "tokio/net", "socket2", "mio" ] +default = [ "host-net", "remote", "json", "messagepack", "cbor", "hyper", "tokio-tungstenite" ] +host-net = [ "tokio", "libc", "tokio/io-util", "virtual-mio/sys", "tokio/net", "tokio/rt", "socket2", "mio" ] +remote = [ "tokio", "libc", "tokio/io-util", "tokio/sync", "tokio-serde", "tokio-util" ] +json = [ "tokio-serde/json" ] +messagepack = [ "tokio-serde/messagepack" ] +cbor = [ "tokio-serde/cbor" ] +hyper = [ "hyper-tungstenite", "dep:hyper" ] +tokio-tungstenite = [ "dep:tokio-tungstenite" ] [package.metadata.docs.rs] -features = ["host-net"] +features = ["host-net", "remote"] rustc-args = ["--cfg", "docsrs"] diff --git a/lib/virtual-net/src/client.rs b/lib/virtual-net/src/client.rs new file mode 100644 index 00000000000..375985f1c54 --- /dev/null +++ b/lib/virtual-net/src/client.rs @@ -0,0 +1,1399 @@ +use std::collections::HashMap; +use std::future::Future; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::sync::Mutex; +use std::task::Context; +use std::task::Poll; +use std::task::RawWaker; +use std::task::RawWakerVTable; +use std::task::Waker; +use std::time::Duration; + +use bytes::Buf; +use bytes::BytesMut; +use derivative::Derivative; +use futures_util::future::BoxFuture; +use futures_util::stream::FuturesOrdered; +use futures_util::Sink; +use futures_util::Stream; +use futures_util::StreamExt; +use tokio::io::AsyncRead; +use tokio::io::AsyncWrite; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::TryRecvError; +use tokio::sync::mpsc::error::TrySendError; +use tokio_serde::formats::SymmetricalBincode; +#[cfg(feature = "cbor")] +use tokio_serde::formats::SymmetricalCbor; +#[cfg(feature = "json")] +use tokio_serde::formats::SymmetricalJson; +#[cfg(feature = "messagepack")] +use tokio_serde::formats::SymmetricalMessagePack; +use tokio_serde::SymmetricallyFramed; +use tokio_util::codec::FramedRead; +use tokio_util::codec::FramedWrite; +use tokio_util::codec::LengthDelimitedCodec; +use virtual_mio::InlineWaker; +use virtual_mio::InterestType; + +use crate::meta; +use crate::meta::FrameSerializationFormat; +use crate::meta::RequestType; +use crate::meta::ResponseType; +use crate::meta::SocketId; +use crate::meta::{MessageRequest, MessageResponse}; +use crate::IpCidr; +use crate::IpRoute; +use crate::NetworkError; +use crate::StreamSecurity; +use crate::VirtualConnectedSocket; +use crate::VirtualConnectionlessSocket; +use crate::VirtualIcmpSocket; +use crate::VirtualIoSource; +use crate::VirtualNetworking; +use crate::VirtualRawSocket; +use crate::VirtualSocket; +use crate::VirtualTcpListener; +use crate::VirtualTcpSocket; +use crate::VirtualUdpSocket; + +use crate::rx_tx::RemoteRx; +use crate::rx_tx::RemoteTx; +use crate::rx_tx::RemoteTxWakers; +use crate::Result; + +#[derive(Debug, Clone)] +pub struct RemoteNetworkingClient { + common: Arc, +} + +impl RemoteNetworkingClient { + fn new( + tx: RemoteTx, + rx: RemoteRx, + rx_work: mpsc::UnboundedReceiver>, + ) -> (Self, RemoteNetworkingClientDriver) { + let common = RemoteCommon { + tx, + rx: Mutex::new(rx), + request_seed: AtomicU64::new(1), + requests: Default::default(), + socket_seed: AtomicU64::new(1), + recv_tx: Default::default(), + recv_with_addr_tx: Default::default(), + accept_tx: Default::default(), + handlers: Default::default(), + stall: Default::default(), + }; + let common = Arc::new(common); + + let driver = RemoteNetworkingClientDriver { + more_work: rx_work, + tasks: Default::default(), + common: common.clone(), + }; + let networking = Self { common }; + + (networking, driver) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + pub fn new_from_mpsc( + tx: mpsc::Sender, + rx: mpsc::Receiver, + ) -> (Self, RemoteNetworkingClientDriver) { + let (tx_work, rx_work) = mpsc::unbounded_channel(); + let tx_wakers = RemoteTxWakers::default(); + + let tx = RemoteTx::Mpsc { + tx, + work: tx_work, + wakers: tx_wakers.clone(), + }; + let rx = RemoteRx::Mpsc { + rx, + wakers: tx_wakers, + }; + + Self::new(tx, rx, rx_work) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + /// + /// This version will run the async read and write operations + /// only the driver (this is needed for mixed runtimes) + pub fn new_from_async_io( + tx: TX, + rx: RX, + format: FrameSerializationFormat, + ) -> (Self, RemoteNetworkingClientDriver) + where + TX: AsyncWrite + Send + 'static, + RX: AsyncRead + Send + 'static, + { + let tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); + let tx: Pin + Send + 'static>> = + match format { + FrameSerializationFormat::Bincode => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalBincode::default())) + } + #[cfg(feature = "json")] + FrameSerializationFormat::Json => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalJson::default())) + } + #[cfg(feature = "messagepack")] + FrameSerializationFormat::MessagePack => Box::pin(SymmetricallyFramed::new( + tx, + SymmetricalMessagePack::default(), + )), + #[cfg(feature = "cbor")] + FrameSerializationFormat::Cbor => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalCbor::default())) + } + }; + + let rx = FramedRead::new(rx, LengthDelimitedCodec::new()); + let rx: Pin> + Send + 'static>> = + match format { + FrameSerializationFormat::Bincode => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalBincode::default())) + } + #[cfg(feature = "json")] + FrameSerializationFormat::Json => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalJson::default())) + } + #[cfg(feature = "messagepack")] + FrameSerializationFormat::MessagePack => Box::pin(SymmetricallyFramed::new( + rx, + SymmetricalMessagePack::default(), + )), + #[cfg(feature = "cbor")] + FrameSerializationFormat::Cbor => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalCbor::default())) + } + }; + + let (tx_work, rx_work) = mpsc::unbounded_channel(); + let tx_wakers = RemoteTxWakers::default(); + + let tx = RemoteTx::Stream { + tx: Arc::new(tokio::sync::Mutex::new(tx)), + work: tx_work, + wakers: tx_wakers, + }; + let rx = RemoteRx::Stream { rx }; + + Self::new(tx, rx, rx_work) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + #[cfg(feature = "hyper")] + pub fn new_from_hyper_ws_io( + tx: futures_util::stream::SplitSink< + hyper_tungstenite::WebSocketStream, + hyper_tungstenite::tungstenite::Message, + >, + rx: futures_util::stream::SplitStream< + hyper_tungstenite::WebSocketStream, + >, + format: FrameSerializationFormat, + ) -> (Self, RemoteNetworkingClientDriver) { + let (tx_work, rx_work) = mpsc::unbounded_channel(); + + let tx = RemoteTx::HyperWebSocket { + tx: Arc::new(tokio::sync::Mutex::new(tx)), + work: tx_work, + wakers: RemoteTxWakers::default(), + format, + }; + let rx = RemoteRx::HyperWebSocket { rx, format }; + Self::new(tx, rx, rx_work) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + #[cfg(feature = "tokio-tungstenite")] + pub fn new_from_tokio_ws_io( + tx: futures_util::stream::SplitSink< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + tokio_tungstenite::tungstenite::Message, + >, + rx: futures_util::stream::SplitStream< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + >, + format: FrameSerializationFormat, + ) -> (Self, RemoteNetworkingClientDriver) { + let (tx_work, rx_work) = mpsc::unbounded_channel(); + + let tx = RemoteTx::TokioWebSocket { + tx: Arc::new(tokio::sync::Mutex::new(tx)), + work: tx_work, + wakers: RemoteTxWakers::default(), + format, + }; + let rx = RemoteRx::TokioWebSocket { rx, format }; + Self::new(tx, rx, rx_work) + } + + fn new_socket(&self, id: SocketId) -> RemoteSocket { + let (tx, rx_recv) = tokio::sync::mpsc::channel(100); + self.common.recv_tx.lock().unwrap().insert(id, tx); + + let (tx, rx_recv_with_addr) = tokio::sync::mpsc::channel(100); + self.common.recv_with_addr_tx.lock().unwrap().insert(id, tx); + + let (tx, rx_accept) = tokio::sync::mpsc::channel(100); + self.common.accept_tx.lock().unwrap().insert(id, tx); + + RemoteSocket { + socket_id: id, + common: self.common.clone(), + rx_buffer: BytesMut::new(), + rx_recv, + rx_recv_with_addr, + rx_accept, + tx_waker: TxWaker::new(&self.common).as_waker(), + pending_accept: None, + } + } +} + +pin_project_lite::pin_project! { + pub struct RemoteNetworkingClientDriver { + common: Arc, + more_work: mpsc::UnboundedReceiver>, + #[pin] + tasks: FuturesOrdered>, + } +} + +impl Future for RemoteNetworkingClientDriver { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // This guard will be held while the pipeline is not currently + // stalled by some back pressure. It is only acquired when there + // is background tasks being processed + let mut not_stalled_guard = None; + + // We loop until the waker is registered with the receiving stream + // and all the background tasks + loop { + // Background tasks are sent to this driver in certain circumstances + while let Poll::Ready(Some(work)) = Pin::new(&mut self.more_work).poll_recv(cx) { + self.tasks.push_back(work); + } + + // Background work basically stalls the stream until its all processed + // which makes the back pressure system work properly + match self.tasks.poll_next_unpin(cx) { + Poll::Ready(Some(_)) => continue, + Poll::Ready(None) => { + not_stalled_guard.take(); + } + Poll::Pending if not_stalled_guard.is_none() => { + if let Ok(guard) = self.common.stall.clone().try_lock_owned() { + not_stalled_guard.replace(guard); + } else { + return Poll::Pending; + } + } + Poll::Pending => {} + }; + + // We grab the next message sent by the server to us + let msg = { + let mut rx_guard = self.common.rx.lock().unwrap(); + rx_guard.poll(cx) + }; + return match msg { + Poll::Ready(Some(msg)) => { + match msg { + MessageResponse::Recv { socket_id, data } => { + let tx = { + let guard = self.common.recv_tx.lock().unwrap(); + match guard.get(&socket_id) { + Some(tx) => tx.clone(), + None => continue, + } + }; + let common = self.common.clone(); + self.tasks.push_back(Box::pin(async move { + tx.send(data).await.ok(); + + if let Some(h) = common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Readable) + } + })); + } + MessageResponse::RecvWithAddr { + socket_id, + data, + addr, + } => { + let tx = { + let guard = self.common.recv_with_addr_tx.lock().unwrap(); + match guard.get(&socket_id) { + Some(tx) => tx.clone(), + None => continue, + } + }; + let common = self.common.clone(); + self.tasks.push_back(Box::pin(async move { + tx.send(DataWithAddr { data, addr }).await.ok(); + + if let Some(h) = common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Readable) + } + })); + } + MessageResponse::Sent { socket_id, .. } => { + if let Some(h) = + self.common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Writable) + } + } + MessageResponse::SendError { + socket_id, error, .. + } => match &error { + NetworkError::ConnectionAborted + | NetworkError::ConnectionReset + | NetworkError::BrokenPipe => { + if let Some(h) = + self.common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Closed) + } + } + _ => { + if let Some(h) = + self.common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Writable) + } + } + }, + + MessageResponse::FinishAccept { + socket_id, + child_id, + addr, + } => { + let common = self.common.clone(); + self.tasks.push_back(Box::pin(async move { + let tx = common.accept_tx.lock().unwrap().get(&socket_id).cloned(); + if let Some(tx) = tx { + tx.send(SocketWithAddr { + socket: child_id, + addr, + }) + .await + .ok(); + } + + if let Some(h) = common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Readable) + } + })); + } + MessageResponse::Closed { socket_id } => { + if let Some(h) = + self.common.handlers.lock().unwrap().get_mut(&socket_id) + { + h.interest(InterestType::Closed) + } + } + MessageResponse::ResponseToRequest { req_id, res } => { + let mut requests = self.common.requests.lock().unwrap(); + if let Some(request) = requests.remove(&req_id) { + request.try_send(res).ok(); + } + } + } + continue; + } + Poll::Ready(None) => Poll::Ready(()), + Poll::Pending => Poll::Pending, + }; + } + } +} + +#[derive(Debug)] +struct TxWaker { + common: Arc, +} +impl TxWaker { + pub fn new(common: &Arc) -> Arc { + Arc::new(Self { + common: common.clone(), + }) + } + + fn wake_now(&self) { + let mut guard = self.common.handlers.lock().unwrap(); + for (_, handler) in guard.iter_mut() { + handler.interest(InterestType::Writable); + } + } + + pub fn as_waker(self: &Arc) -> Waker { + let s: *const Self = Arc::into_raw(Arc::clone(self)); + let raw_waker = RawWaker::new(s as *const (), &VTABLE); + unsafe { Waker::from_raw(raw_waker) } + } +} + +fn tx_waker_wake(s: &TxWaker) { + let waker_arc = unsafe { Arc::from_raw(s) }; + waker_arc.wake_now(); +} + +fn tx_waker_clone(s: &TxWaker) -> RawWaker { + let arc = unsafe { Arc::from_raw(s) }; + std::mem::forget(arc.clone()); + RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE) +} + +const VTABLE: RawWakerVTable = unsafe { + RawWakerVTable::new( + |s| tx_waker_clone(&*(s as *const TxWaker)), // clone + |s| tx_waker_wake(&*(s as *const TxWaker)), // wake + |s| (*(s as *const TxWaker)).wake_now(), // wake by ref (don't decrease refcount) + |s| drop(Arc::from_raw(s as *const TxWaker)), // decrease refcount + ) +}; + +#[derive(Debug)] +struct RequestTx { + tx: mpsc::Sender, +} +impl RequestTx { + pub fn try_send(self, msg: ResponseType) -> Result<()> { + match self.tx.try_send(msg) { + Ok(()) => Ok(()), + Err(TrySendError::Closed(_)) => Err(NetworkError::ConnectionAborted), + Err(TrySendError::Full(_)) => Err(NetworkError::WouldBlock), + } + } +} + +#[derive(Debug)] +struct DataWithAddr { + pub data: Vec, + pub addr: SocketAddr, +} +#[derive(Debug)] +struct SocketWithAddr { + pub socket: SocketId, + pub addr: SocketAddr, +} +type SocketMap = HashMap; + +#[derive(Derivative)] +#[derivative(Debug)] +struct RemoteCommon { + #[derivative(Debug = "ignore")] + tx: RemoteTx, + #[derivative(Debug = "ignore")] + rx: Mutex>, + request_seed: AtomicU64, + requests: Mutex>, + socket_seed: AtomicU64, + recv_tx: Mutex>>>, + recv_with_addr_tx: Mutex>>, + accept_tx: Mutex>>, + #[derivative(Debug = "ignore")] + handlers: Mutex>>, + + // The stall guard will prevent reads while its held and there are background tasks running + // (the idea behind this is to create back pressure so that the task list infinitely grow) + stall: Arc>, +} + +impl RemoteCommon { + async fn io_iface(&self, req: RequestType) -> ResponseType { + let req_id = self.request_seed.fetch_add(1, Ordering::SeqCst); + let mut req_rx = { + let (tx, rx) = mpsc::channel(1); + let mut guard = self.requests.lock().unwrap(); + guard.insert(req_id, RequestTx { tx }); + rx + }; + if let Err(err) = self + .tx + .send(MessageRequest::Interface { + req_id: Some(req_id), + req, + }) + .await + { + return ResponseType::Err(err); + }; + req_rx.recv().await.unwrap() + } + + fn io_iface_fire_and_forget(&self, req: RequestType) -> Result<()> { + self.tx + .send_with_driver(MessageRequest::Interface { req_id: None, req }) + } +} + +#[async_trait::async_trait] +impl VirtualNetworking for RemoteNetworkingClient { + async fn bridge( + &self, + network: &str, + access_token: &str, + security: StreamSecurity, + ) -> Result<()> { + match self + .common + .io_iface(RequestType::Bridge { + network: network.to_string(), + access_token: access_token.to_string(), + security, + }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(()), + res => { + tracing::debug!("invalid response to bridge request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn unbridge(&self) -> Result<()> { + match self.common.io_iface(RequestType::Unbridge).await { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(()), + res => { + tracing::debug!("invalid response to unbridge request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn dhcp_acquire(&self) -> Result> { + match self.common.io_iface(RequestType::DhcpAcquire).await { + ResponseType::Err(err) => Err(err), + ResponseType::IpAddressList(ips) => Ok(ips), + res => { + tracing::debug!("invalid response to DHCP acquire request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> { + self.common + .io_iface_fire_and_forget(RequestType::IpAdd { ip, prefix }) + } + + async fn ip_remove(&self, ip: IpAddr) -> Result<()> { + self.common + .io_iface_fire_and_forget(RequestType::IpRemove(ip)) + } + + async fn ip_clear(&self) -> Result<()> { + self.common.io_iface_fire_and_forget(RequestType::IpClear) + } + + async fn ip_list(&self) -> Result> { + match self.common.io_iface(RequestType::GetIpList).await { + ResponseType::Err(err) => Err(err), + ResponseType::CidrList(routes) => Ok(routes), + res => { + tracing::debug!("invalid response to IP list request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn mac(&self) -> Result<[u8; 6]> { + match self.common.io_iface(RequestType::GetMac).await { + ResponseType::Err(err) => Err(err), + ResponseType::Mac(mac) => Ok(mac), + res => { + tracing::debug!("invalid response to MAC request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn gateway_set(&self, ip: IpAddr) -> Result<()> { + self.common + .io_iface_fire_and_forget(RequestType::GatewaySet(ip)) + } + + async fn route_add( + &self, + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + ) -> Result<()> { + self.common.io_iface_fire_and_forget(RequestType::RouteAdd { + cidr, + via_router, + preferred_until, + expires_at, + }) + } + + async fn route_remove(&self, cidr: IpAddr) -> Result<()> { + self.common + .io_iface_fire_and_forget(RequestType::RouteRemove(cidr)) + } + + async fn route_clear(&self) -> Result<()> { + self.common + .io_iface_fire_and_forget(RequestType::RouteClear) + } + + async fn route_list(&self) -> Result> { + match self.common.io_iface(RequestType::GetRouteList).await { + ResponseType::Err(err) => Err(err), + ResponseType::RouteList(routes) => Ok(routes), + res => { + tracing::debug!("invalid response to route list request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn bind_raw(&self) -> Result> { + let socket_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + match self.common.io_iface(RequestType::BindRaw(socket_id)).await { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(Box::new(self.new_socket(socket_id))), + ResponseType::Socket(socket_id) => Ok(Box::new(self.new_socket(socket_id))), + res => { + tracing::debug!("invalid response to bind RAw request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn listen_tcp( + &self, + addr: SocketAddr, + only_v6: bool, + reuse_port: bool, + reuse_addr: bool, + ) -> Result> { + let socket_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + match self + .common + .io_iface(RequestType::ListenTcp { + socket_id, + addr, + only_v6, + reuse_port, + reuse_addr, + }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(Box::new(self.new_socket(socket_id))), + ResponseType::Socket(socket_id) => { + let mut socket = self.new_socket(socket_id); + socket.touch_begin_accept().ok(); + Ok(Box::new(socket)) + } + res => { + tracing::debug!("invalid response to listen TCP request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn bind_udp( + &self, + addr: SocketAddr, + reuse_port: bool, + reuse_addr: bool, + ) -> Result> { + let socket_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + match self + .common + .io_iface(RequestType::BindUdp { + socket_id, + addr, + reuse_port, + reuse_addr, + }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(Box::new(self.new_socket(socket_id))), + ResponseType::Socket(socket_id) => Ok(Box::new(self.new_socket(socket_id))), + res => { + tracing::debug!("invalid response to bind UDP request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn bind_icmp(&self, addr: IpAddr) -> Result> { + let socket_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + match self + .common + .io_iface(RequestType::BindIcmp { socket_id, addr }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(Box::new(self.new_socket(socket_id))), + ResponseType::Socket(socket_id) => Ok(Box::new(self.new_socket(socket_id))), + res => { + tracing::debug!("invalid response to bind ICMP request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn connect_tcp( + &self, + addr: SocketAddr, + peer: SocketAddr, + ) -> Result> { + let socket_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + match self + .common + .io_iface(RequestType::ConnectTcp { + socket_id, + addr, + peer, + }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(Box::new(self.new_socket(socket_id))), + ResponseType::Socket(socket_id) => Ok(Box::new(self.new_socket(socket_id))), + res => { + tracing::debug!("invalid response to connect TCP request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + async fn resolve( + &self, + host: &str, + port: Option, + dns_server: Option, + ) -> Result> { + match self + .common + .io_iface(RequestType::Resolve { + host: host.to_string(), + port, + dns_server, + }) + .await + { + ResponseType::Err(err) => Err(err), + ResponseType::IpAddressList(ips) => Ok(ips), + res => { + tracing::debug!("invalid response to resolve request - {res:?}"); + Err(NetworkError::IOError) + } + } + } +} + +#[derive(Debug)] +struct RemoteSocket { + socket_id: SocketId, + common: Arc, + rx_buffer: BytesMut, + rx_recv: mpsc::Receiver>, + rx_recv_with_addr: mpsc::Receiver, + tx_waker: Waker, + rx_accept: mpsc::Receiver, + pending_accept: Option, +} +impl Drop for RemoteSocket { + fn drop(&mut self) { + self.common.recv_tx.lock().unwrap().remove(&self.socket_id); + self.common + .recv_with_addr_tx + .lock() + .unwrap() + .remove(&self.socket_id); + } +} + +impl RemoteSocket { + async fn io_socket(&self, req: RequestType) -> ResponseType { + let req_id = self.common.request_seed.fetch_add(1, Ordering::SeqCst); + let mut req_rx = { + let (tx, rx) = mpsc::channel(1); + let mut guard = self.common.requests.lock().unwrap(); + guard.insert(req_id, RequestTx { tx }); + rx + }; + if let Err(err) = self + .common + .tx + .send(MessageRequest::Socket { + socket: self.socket_id, + req_id: Some(req_id), + req, + }) + .await + { + return ResponseType::Err(err); + }; + req_rx.recv().await.unwrap() + } + + fn io_socket_fire_and_forget(&self, req: RequestType) -> Result<()> { + self.common.tx.send_with_driver(MessageRequest::Socket { + socket: self.socket_id, + req_id: None, + req, + }) + } + + fn touch_begin_accept(&mut self) -> Result<()> { + if self.pending_accept.is_some() { + return Ok(()); + } + let child_id: SocketId = self + .common + .socket_seed + .fetch_add(1, Ordering::SeqCst) + .into(); + self.io_socket_fire_and_forget(RequestType::BeginAccept(child_id))?; + self.pending_accept.replace(child_id); + Ok(()) + } +} + +impl VirtualIoSource for RemoteSocket { + fn remove_handler(&mut self) { + self.common.handlers.lock().unwrap().remove(&self.socket_id); + } +} + +impl VirtualSocket for RemoteSocket { + fn set_ttl(&mut self, ttl: u32) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetTtl(ttl)) + } + + fn ttl(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetTtl)) { + ResponseType::Err(err) => Err(err), + ResponseType::Ttl(ttl) => Ok(ttl), + res => { + tracing::debug!("invalid response to get TTL request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn addr_local(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetAddrLocal)) { + ResponseType::Err(err) => Err(err), + ResponseType::SocketAddr(addr) => Ok(addr), + res => { + tracing::debug!("invalid response to address local request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn status(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetStatus)) { + ResponseType::Err(err) => Err(err), + ResponseType::Status(status) => Ok(status), + res => { + tracing::debug!("invalid response to status request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_handler( + &mut self, + handler: Box, + ) -> Result<()> { + self.common + .handlers + .lock() + .unwrap() + .insert(self.socket_id, handler); + Ok(()) + } +} + +impl VirtualTcpListener for RemoteSocket { + fn try_accept(&mut self) -> Result<(Box, SocketAddr)> { + self.touch_begin_accept()?; + + let accepted = self.rx_accept.try_recv().map_err(|err| match err { + TryRecvError::Empty => NetworkError::WouldBlock, + TryRecvError::Disconnected => NetworkError::ConnectionAborted, + })?; + + // This placed here will mean there is always an accept request pending at the + // server as the constructor invokes this method and we invoke it here after + // receiving a child connection. + self.pending_accept.take(); + self.touch_begin_accept().ok(); + + // Now we construct the child + let (tx, rx_recv) = tokio::sync::mpsc::channel(100); + self.common + .recv_tx + .lock() + .unwrap() + .insert(accepted.socket, tx); + + let (tx, rx_recv_with_addr) = tokio::sync::mpsc::channel(100); + self.common + .recv_with_addr_tx + .lock() + .unwrap() + .insert(accepted.socket, tx); + + let (tx, rx_accept) = tokio::sync::mpsc::channel(100); + self.common + .accept_tx + .lock() + .unwrap() + .insert(accepted.socket, tx); + + let socket = RemoteSocket { + socket_id: accepted.socket, + common: self.common.clone(), + rx_buffer: BytesMut::new(), + rx_recv, + rx_recv_with_addr, + rx_accept, + pending_accept: None, + tx_waker: TxWaker::new(&self.common).as_waker(), + }; + Ok((Box::new(socket), accepted.addr)) + } + + fn set_handler( + &mut self, + handler: Box, + ) -> Result<()> { + VirtualSocket::set_handler(self, handler) + } + + fn addr_local(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetAddrLocal)) { + ResponseType::Err(err) => Err(err), + ResponseType::SocketAddr(addr) => Ok(addr), + res => { + tracing::debug!("invalid response to addr local request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_ttl(&mut self, ttl: u8) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetTtl(ttl as u32)) + } + + fn ttl(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetTtl)) { + ResponseType::Err(err) => Err(err), + ResponseType::Ttl(val) => Ok(val.try_into().map_err(|_| NetworkError::InvalidData)?), + res => { + tracing::debug!("invalid response to get TTL request - {res:?}"); + Err(NetworkError::IOError) + } + } + } +} + +impl VirtualRawSocket for RemoteSocket { + fn try_send(&mut self, data: &[u8]) -> Result { + let mut cx = Context::from_waker(&self.tx_waker); + match self.common.tx.poll_send( + &mut cx, + MessageRequest::Send { + socket: self.socket_id, + data: data.to_vec(), + req_id: None, + }, + ) { + Poll::Ready(Ok(())) => Ok(data.len()), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock), + } + } + + fn try_flush(&mut self) -> Result<()> { + let mut cx = Context::from_waker(&self.tx_waker); + match self.common.tx.poll_send( + &mut cx, + MessageRequest::Socket { + socket: self.socket_id, + req: RequestType::Flush, + req_id: None, + }, + ) { + Poll::Ready(Ok(())) => Ok(()), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock), + } + } + + fn try_recv(&mut self, buf: &mut [std::mem::MaybeUninit]) -> Result { + loop { + if !self.rx_buffer.is_empty() { + let amt = self.rx_buffer.len().min(buf.len()); + let buf: &mut [u8] = unsafe { std::mem::transmute(buf) }; + buf[..amt].copy_from_slice(&self.rx_buffer[..amt]); + self.rx_buffer.advance(amt); + return Ok(amt); + } + match self.rx_recv.try_recv() { + Ok(data) => self.rx_buffer.extend_from_slice(&data), + Err(TryRecvError::Disconnected) => return Err(NetworkError::ConnectionAborted), + Err(TryRecvError::Empty) => return Err(NetworkError::WouldBlock), + } + } + } + + fn set_promiscuous(&mut self, promiscuous: bool) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetPromiscuous(promiscuous)) + } + + fn promiscuous(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetPromiscuous)) { + ResponseType::Err(err) => Err(err), + ResponseType::Flag(val) => Ok(val), + res => { + tracing::debug!("invalid response to get promiscuous request - {res:?}"); + Err(NetworkError::IOError) + } + } + } +} + +impl VirtualConnectionlessSocket for RemoteSocket { + fn try_send_to(&mut self, data: &[u8], addr: SocketAddr) -> Result { + let req_id = self.common.request_seed.fetch_add(1, Ordering::SeqCst); + let mut cx = Context::from_waker(&self.tx_waker); + match self.common.tx.poll_send( + &mut cx, + MessageRequest::SendTo { + socket: self.socket_id, + data: data.to_vec(), + addr, + req_id: Some(req_id), + }, + ) { + Poll::Ready(Ok(())) => Ok(data.len()), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock), + } + } + + fn try_recv_from( + &mut self, + buf: &mut [std::mem::MaybeUninit], + ) -> Result<(usize, SocketAddr)> { + match self.rx_recv_with_addr.try_recv() { + Ok(received) => { + let amt = buf.len().min(received.data.len()); + let buf: &mut [u8] = unsafe { std::mem::transmute(buf) }; + buf[..amt].copy_from_slice(&received.data[..amt]); + Ok((amt, received.addr)) + } + Err(TryRecvError::Disconnected) => Err(NetworkError::ConnectionAborted), + Err(TryRecvError::Empty) => Err(NetworkError::WouldBlock), + } + } +} + +impl VirtualUdpSocket for RemoteSocket { + fn set_broadcast(&mut self, broadcast: bool) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetBroadcast(broadcast)) + } + + fn broadcast(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetBroadcast)) { + ResponseType::Err(err) => Err(err), + ResponseType::Flag(val) => Ok(val), + res => { + tracing::debug!("invalid response to get broadcast request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_multicast_loop_v4(&mut self, val: bool) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetMulticastLoopV4(val)) + } + + fn multicast_loop_v4(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetMulticastLoopV4)) { + ResponseType::Err(err) => Err(err), + ResponseType::Flag(val) => Ok(val), + res => { + tracing::debug!("invalid response to get multicast loop v4 request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_multicast_loop_v6(&mut self, val: bool) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetMulticastLoopV6(val)) + } + + fn multicast_loop_v6(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetMulticastLoopV6)) { + ResponseType::Err(err) => Err(err), + ResponseType::Flag(val) => Ok(val), + res => { + tracing::debug!("invalid response to get multicast loop v6 request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_multicast_ttl_v4(&mut self, ttl: u32) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetMulticastTtlV4(ttl)) + } + + fn multicast_ttl_v4(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetMulticastTtlV4)) { + ResponseType::Err(err) => Err(err), + ResponseType::Ttl(ttl) => Ok(ttl), + res => { + tracing::debug!("invalid response to get multicast TTL v4 request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn join_multicast_v4( + &mut self, + multiaddr: std::net::Ipv4Addr, + iface: std::net::Ipv4Addr, + ) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::JoinMulticastV4 { multiaddr, iface }) + } + + fn leave_multicast_v4( + &mut self, + multiaddr: std::net::Ipv4Addr, + iface: std::net::Ipv4Addr, + ) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::LeaveMulticastV4 { multiaddr, iface }) + } + + fn join_multicast_v6(&mut self, multiaddr: std::net::Ipv6Addr, iface: u32) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::JoinMulticastV6 { multiaddr, iface }) + } + + fn leave_multicast_v6(&mut self, multiaddr: std::net::Ipv6Addr, iface: u32) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::LeaveMulticastV6 { multiaddr, iface }) + } + + fn addr_peer(&self) -> Result> { + match InlineWaker::block_on(self.io_socket(RequestType::GetAddrPeer)) { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(None), + ResponseType::SocketAddr(addr) => Ok(Some(addr)), + res => { + tracing::debug!("invalid response to addr peer request - {res:?}"); + Err(NetworkError::IOError) + } + } + } +} + +impl VirtualIcmpSocket for RemoteSocket {} + +impl VirtualConnectedSocket for RemoteSocket { + fn set_linger(&mut self, linger: Option) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetLinger(linger)) + } + + fn linger(&self) -> Result> { + match InlineWaker::block_on(self.io_socket(RequestType::GetLinger)) { + ResponseType::Err(err) => Err(err), + ResponseType::None => Ok(None), + ResponseType::Duration(val) => Ok(Some(val)), + res => { + tracing::debug!("invalid response to get linger request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn try_send(&mut self, data: &[u8]) -> Result { + let req_id = self.common.request_seed.fetch_add(1, Ordering::SeqCst); + let mut cx = Context::from_waker(&self.tx_waker); + match self.common.tx.poll_send( + &mut cx, + MessageRequest::Send { + socket: self.socket_id, + data: data.to_vec(), + req_id: Some(req_id), + }, + ) { + Poll::Ready(Ok(())) => Ok(data.len()), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock), + } + } + + fn try_flush(&mut self) -> Result<()> { + let mut cx = Context::from_waker(&self.tx_waker); + match self.common.tx.poll_send( + &mut cx, + MessageRequest::Socket { + socket: self.socket_id, + req: RequestType::Flush, + req_id: None, + }, + ) { + Poll::Ready(Ok(())) => Ok(()), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => Err(NetworkError::WouldBlock), + } + } + + fn close(&mut self) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::Close) + } + + fn try_recv(&mut self, buf: &mut [std::mem::MaybeUninit]) -> Result { + loop { + if !self.rx_buffer.is_empty() { + let amt = self.rx_buffer.len().min(buf.len()); + let buf: &mut [u8] = unsafe { std::mem::transmute(buf) }; + buf[..amt].copy_from_slice(&self.rx_buffer[..amt]); + self.rx_buffer.advance(amt); + return Ok(amt); + } + match self.rx_recv.try_recv() { + Ok(data) => self.rx_buffer.extend_from_slice(&data), + Err(TryRecvError::Disconnected) => return Err(NetworkError::ConnectionAborted), + Err(TryRecvError::Empty) => return Err(NetworkError::WouldBlock), + } + } + } +} + +impl VirtualTcpSocket for RemoteSocket { + fn set_recv_buf_size(&mut self, size: usize) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetRecvBufSize(size as u64)) + } + + fn recv_buf_size(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetRecvBufSize)) { + ResponseType::Err(err) => Err(err), + ResponseType::Amount(amt) => Ok(amt.try_into().map_err(|_| NetworkError::IOError)?), + res => { + tracing::debug!("invalid response to get recv buf size request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_send_buf_size(&mut self, size: usize) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetSendBufSize(size as u64)) + } + + fn send_buf_size(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetSendBufSize)) { + ResponseType::Err(err) => Err(err), + ResponseType::Amount(val) => Ok(val.try_into().map_err(|_| NetworkError::IOError)?), + res => { + tracing::debug!("invalid response to get send buf size request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn set_nodelay(&mut self, reuse: bool) -> Result<()> { + self.io_socket_fire_and_forget(RequestType::SetNoDelay(reuse)) + } + + fn nodelay(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetNoDelay)) { + ResponseType::Err(err) => Err(err), + ResponseType::Flag(val) => Ok(val), + res => { + tracing::debug!("invalid response to get nodelay request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn addr_peer(&self) -> Result { + match InlineWaker::block_on(self.io_socket(RequestType::GetAddrPeer)) { + ResponseType::Err(err) => Err(err), + ResponseType::SocketAddr(addr) => Ok(addr), + res => { + tracing::debug!("invalid response to addr peer request - {res:?}"); + Err(NetworkError::IOError) + } + } + } + + fn shutdown(&mut self, how: std::net::Shutdown) -> Result<()> { + let shutdown = match how { + std::net::Shutdown::Read => meta::Shutdown::Read, + std::net::Shutdown::Write => meta::Shutdown::Write, + std::net::Shutdown::Both => meta::Shutdown::Both, + }; + self.io_socket_fire_and_forget(RequestType::Shutdown(shutdown)) + } + + fn is_closed(&self) -> bool { + match InlineWaker::block_on(self.io_socket(RequestType::IsClosed)) { + ResponseType::Flag(val) => val, + _ => false, + } + } +} diff --git a/lib/virtual-net/src/host.rs b/lib/virtual-net/src/host.rs index b10016a4c16..50205bf3970 100644 --- a/lib/virtual-net/src/host.rs +++ b/lib/virtual-net/src/host.rs @@ -1,5 +1,5 @@ #![allow(unused_variables)] -use crate::VirtualIoSource; +use crate::{io_err_into_net_error, VirtualIoSource}; #[allow(unused_imports)] use crate::{ IpCidr, IpRoute, NetworkError, Result, SocketStatus, StreamSecurity, VirtualConnectedSocket, @@ -482,55 +482,3 @@ impl VirtualIoSource for LocalUdpSocket { } } } - -pub fn io_err_into_net_error(net_error: std::io::Error) -> NetworkError { - use std::io::ErrorKind; - match net_error.kind() { - ErrorKind::BrokenPipe => NetworkError::BrokenPipe, - ErrorKind::AlreadyExists => NetworkError::AlreadyExists, - ErrorKind::AddrInUse => NetworkError::AddressInUse, - ErrorKind::AddrNotAvailable => NetworkError::AddressNotAvailable, - ErrorKind::ConnectionAborted => NetworkError::ConnectionAborted, - ErrorKind::ConnectionRefused => NetworkError::ConnectionRefused, - ErrorKind::ConnectionReset => NetworkError::ConnectionReset, - ErrorKind::Interrupted => NetworkError::Interrupted, - ErrorKind::InvalidData => NetworkError::InvalidData, - ErrorKind::InvalidInput => NetworkError::InvalidInput, - ErrorKind::NotConnected => NetworkError::NotConnected, - ErrorKind::PermissionDenied => NetworkError::PermissionDenied, - ErrorKind::TimedOut => NetworkError::TimedOut, - ErrorKind::UnexpectedEof => NetworkError::UnexpectedEof, - ErrorKind::WouldBlock => NetworkError::WouldBlock, - ErrorKind::WriteZero => NetworkError::WriteZero, - ErrorKind::Unsupported => NetworkError::Unsupported, - - #[cfg(target_family = "unix")] - _ => { - if let Some(code) = net_error.raw_os_error() { - match code { - libc::EPERM => NetworkError::PermissionDenied, - libc::EBADF => NetworkError::InvalidFd, - libc::ECHILD => NetworkError::InvalidFd, - libc::EMFILE => NetworkError::TooManyOpenFiles, - libc::EINTR => NetworkError::Interrupted, - libc::EIO => NetworkError::IOError, - libc::ENXIO => NetworkError::IOError, - libc::EAGAIN => NetworkError::WouldBlock, - libc::ENOMEM => NetworkError::InsufficientMemory, - libc::EACCES => NetworkError::PermissionDenied, - libc::ENODEV => NetworkError::NoDevice, - libc::EINVAL => NetworkError::InvalidInput, - libc::EPIPE => NetworkError::BrokenPipe, - err => { - tracing::trace!("unknown os error {}", err); - NetworkError::UnknownError - } - } - } else { - NetworkError::UnknownError - } - } - #[cfg(not(target_family = "unix"))] - _ => NetworkError::UnknownError, - } -} diff --git a/lib/virtual-net/src/lib.rs b/lib/virtual-net/src/lib.rs index 69195912672..dbc59d04ac8 100644 --- a/lib/virtual-net/src/lib.rs +++ b/lib/virtual-net/src/lib.rs @@ -1,18 +1,44 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[cfg(any(feature = "remote"))] +pub mod client; +#[cfg(feature = "host-net")] +pub mod host; +pub mod meta; +#[cfg(any(feature = "remote"))] +pub mod rx_tx; +#[cfg(any(feature = "remote"))] +pub mod server; +#[cfg(feature = "tokio")] +#[cfg(test)] +mod tests; + +#[cfg(any(feature = "remote"))] +pub use client::{RemoteNetworkingClient, RemoteNetworkingClientDriver}; +use pin_project_lite::pin_project; +#[cfg(any(feature = "remote"))] +pub use server::{RemoteNetworkingServer, RemoteNetworkingServerDriver}; use std::fmt; use std::mem::MaybeUninit; -use std::net::IpAddr; -use std::net::Ipv4Addr; -use std::net::Ipv6Addr; +pub use std::net::IpAddr; +pub use std::net::Ipv4Addr; +pub use std::net::Ipv6Addr; use std::net::Shutdown; -use std::net::SocketAddr; +pub use std::net::SocketAddr; +use std::pin::Pin; use std::sync::Arc; -use std::time::Duration; +use std::task::Context; +use std::task::Poll; +pub use std::time::Duration; use thiserror::Error; +#[cfg(feature = "tokio")] +use tokio::io::AsyncRead; +#[cfg(feature = "tokio")] +use tokio::io::AsyncWrite; pub use bytes::Bytes; pub use bytes::BytesMut; +use serde::{Deserialize, Serialize}; pub use virtual_mio::{handler_into_waker, InterestHandler}; #[cfg(feature = "host-net")] pub use virtual_mio::{InterestGuard, InterestHandlerWaker, InterestType}; @@ -20,14 +46,14 @@ pub use virtual_mio::{InterestGuard, InterestHandlerWaker, InterestType}; pub type Result = std::result::Result; /// Represents an IP address and its netmask -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] pub struct IpCidr { pub ip: IpAddr, pub prefix: u8, } /// Represents a routing entry in the routing table of the interface -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct IpRoute { pub cidr: IpCidr, pub via_router: IpAddr, @@ -67,37 +93,37 @@ pub trait VirtualNetworking: fmt::Debug + Send + Sync + 'static { } /// Adds a static IP address to the interface with a netmask prefix - fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> { + async fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> { Err(NetworkError::Unsupported) } /// Removes a static (or dynamic) IP address from the interface - fn ip_remove(&self, ip: IpAddr) -> Result<()> { + async fn ip_remove(&self, ip: IpAddr) -> Result<()> { Err(NetworkError::Unsupported) } /// Clears all the assigned IP addresses for this interface - fn ip_clear(&self) -> Result<()> { + async fn ip_clear(&self) -> Result<()> { Err(NetworkError::Unsupported) } /// Lists all the IP addresses currently assigned to this interface - fn ip_list(&self) -> Result> { + async fn ip_list(&self) -> Result> { Err(NetworkError::Unsupported) } /// Returns the hardware MAC address for this interface - fn mac(&self) -> Result<[u8; 6]> { + async fn mac(&self) -> Result<[u8; 6]> { Err(NetworkError::Unsupported) } /// Adds a default gateway to the routing table - fn gateway_set(&self, ip: IpAddr) -> Result<()> { + async fn gateway_set(&self, ip: IpAddr) -> Result<()> { Err(NetworkError::Unsupported) } /// Adds a specific route to the routing table - fn route_add( + async fn route_add( &self, cidr: IpCidr, via_router: IpAddr, @@ -108,17 +134,17 @@ pub trait VirtualNetworking: fmt::Debug + Send + Sync + 'static { } /// Removes a routing rule from the routing table - fn route_remove(&self, cidr: IpAddr) -> Result<()> { + async fn route_remove(&self, cidr: IpAddr) -> Result<()> { Err(NetworkError::Unsupported) } /// Clears the routing table for this interface - fn route_clear(&self) -> Result<()> { + async fn route_clear(&self) -> Result<()> { Err(NetworkError::Unsupported) } /// Lists all the routes defined in the routing table for this interface - fn route_list(&self) -> Result> { + async fn route_list(&self) -> Result> { Err(NetworkError::Unsupported) } @@ -199,6 +225,42 @@ pub trait VirtualTcpListener: VirtualIoSource + fmt::Debug + Send + Sync + 'stat fn ttl(&self) -> Result; } +#[async_trait::async_trait] +pub trait VirtualTcpListenerExt: VirtualTcpListener { + /// Accepts a new connection from the TCP listener + async fn accept(&mut self) -> Result<(Box, SocketAddr)>; +} + +#[async_trait::async_trait] +impl VirtualTcpListenerExt for R { + async fn accept(&mut self) -> Result<(Box, SocketAddr)> { + struct Poller<'a, R> + where + R: VirtualTcpListener + ?Sized, + { + listener: &'a mut R, + } + impl<'a, R> std::future::Future for Poller<'a, R> + where + R: VirtualTcpListener + ?Sized, + { + type Output = Result<(Box, SocketAddr)>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let handler: Box = cx.waker().into(); + if let Err(err) = self.listener.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match self.listener.try_accept() { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { listener: self }.await + } +} + pub trait VirtualSocket: VirtualIoSource + fmt::Debug + Send + Sync + 'static { /// Sets how many network hops the packets are permitted for new connections fn set_ttl(&mut self, ttl: u32) -> Result<()>; @@ -218,7 +280,7 @@ pub trait VirtualSocket: VirtualIoSource + fmt::Debug + Send + Sync + 'static { fn set_handler(&mut self, handler: Box) -> Result<()>; } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SocketStatus { Opening, Opened, @@ -226,7 +288,7 @@ pub enum SocketStatus { Failed, } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum StreamSecurity { Unencrypted, AnyEncyption, @@ -259,6 +321,109 @@ pub trait VirtualConnectedSocket: VirtualSocket + fmt::Debug + Send + Sync + 'st fn try_recv(&mut self, buf: &mut [MaybeUninit]) -> Result; } +#[async_trait::async_trait] +pub trait VirtualConnectedSocketExt: VirtualConnectedSocket { + async fn send(&mut self, data: &[u8]) -> Result; + + async fn recv(&mut self, buf: &mut [MaybeUninit]) -> Result; + + async fn flush(&mut self) -> Result<()>; +} + +#[async_trait::async_trait] +impl VirtualConnectedSocketExt for R { + async fn send(&mut self, data: &[u8]) -> Result { + pin_project! { + struct Poller<'a, 'b, R: ?Sized> + where + R: VirtualConnectedSocket, + { + socket: &'a mut R, + data: &'b [u8], + } + } + impl<'a, 'b, R> std::future::Future for Poller<'a, 'b, R> + where + R: VirtualConnectedSocket + ?Sized, + { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let handler: Box = cx.waker().into(); + if let Err(err) = this.socket.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match this.socket.try_send(this.data) { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { socket: self, data }.await + } + + async fn recv(&mut self, buf: &mut [MaybeUninit]) -> Result { + pin_project! { + struct Poller<'a, 'b, R: ?Sized> + where + R: VirtualConnectedSocket, + { + socket: &'a mut R, + buf: &'b mut [MaybeUninit], + } + } + impl<'a, 'b, R> std::future::Future for Poller<'a, 'b, R> + where + R: VirtualConnectedSocket + ?Sized, + { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let handler: Box = cx.waker().into(); + if let Err(err) = this.socket.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match this.socket.try_recv(this.buf) { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { socket: self, buf }.await + } + + async fn flush(&mut self) -> Result<()> { + struct Poller<'a, R> + where + R: VirtualConnectedSocket + ?Sized, + { + socket: &'a mut R, + } + impl<'a, R> std::future::Future for Poller<'a, R> + where + R: VirtualConnectedSocket + ?Sized, + { + type Output = Result<()>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let handler: Box = cx.waker().into(); + if let Err(err) = self.socket.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match self.socket.try_flush() { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { socket: self }.await + } +} + /// Connectionless sockets are able to send and receive datagrams and stream /// bytes to multiple addresses at the same time (peer-to-peer) pub trait VirtualConnectionlessSocket: VirtualSocket + fmt::Debug + Send + Sync + 'static { @@ -270,6 +435,86 @@ pub trait VirtualConnectionlessSocket: VirtualSocket + fmt::Debug + Send + Sync fn try_recv_from(&mut self, buf: &mut [MaybeUninit]) -> Result<(usize, SocketAddr)>; } +#[async_trait::async_trait] +pub trait VirtualConnectionlessSocketExt: VirtualConnectionlessSocket { + async fn send_to(&mut self, data: &[u8], addr: SocketAddr) -> Result; + + async fn recv_from(&mut self, buf: &mut [MaybeUninit]) -> Result<(usize, SocketAddr)>; +} + +#[async_trait::async_trait] +impl VirtualConnectionlessSocketExt for R { + async fn send_to(&mut self, data: &[u8], addr: SocketAddr) -> Result { + pin_project! { + struct Poller<'a, 'b, R: ?Sized> + where + R: VirtualConnectionlessSocket, + { + socket: &'a mut R, + data: &'b [u8], + addr: SocketAddr, + } + } + impl<'a, 'b, R> std::future::Future for Poller<'a, 'b, R> + where + R: VirtualConnectionlessSocket + ?Sized, + { + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let handler: Box = cx.waker().into(); + if let Err(err) = this.socket.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match this.socket.try_send_to(this.data, *this.addr) { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { + socket: self, + data, + addr, + } + .await + } + + async fn recv_from(&mut self, buf: &mut [MaybeUninit]) -> Result<(usize, SocketAddr)> { + pin_project! { + struct Poller<'a, 'b, R: ?Sized> + where + R: VirtualConnectionlessSocket, + { + socket: &'a mut R, + buf: &'b mut [MaybeUninit], + } + } + impl<'a, 'b, R> std::future::Future for Poller<'a, 'b, R> + where + R: VirtualConnectionlessSocket + ?Sized, + { + type Output = Result<(usize, SocketAddr)>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let handler: Box = cx.waker().into(); + if let Err(err) = this.socket.set_handler(handler) { + return Poll::Ready(Err(err)); + } + match this.socket.try_recv_from(this.buf) { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } + } + Poller { socket: self, buf }.await + } +} + /// ICMP sockets are low level devices bound to a specific address /// that can send and receive ICMP packets pub trait VirtualIcmpSocket: @@ -277,6 +522,7 @@ pub trait VirtualIcmpSocket: { } +#[async_trait::async_trait] pub trait VirtualRawSocket: VirtualSocket + fmt::Debug + Send + Sync + 'static { /// Sends out a datagram or stream of bytes on this socket fn try_send(&mut self, data: &[u8]) -> Result; @@ -339,6 +585,71 @@ pub trait VirtualTcpSocket: VirtualConnectedSocket + fmt::Debug + Send + Sync + fn is_closed(&self) -> bool; } +#[cfg(feature = "tokio")] +impl<'a> AsyncRead for Box { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + let handler: Box = cx.waker().into(); + if let Err(err) = this.set_handler(handler) { + return Poll::Ready(Err(net_error_into_io_err(err))); + } + let buf_unsafe = unsafe { buf.unfilled_mut() }; + match this.try_recv(buf_unsafe) { + Ok(ret) => { + unsafe { buf.assume_init(ret) }; + buf.set_filled(ret); + Poll::Ready(Ok(())) + } + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(net_error_into_io_err(err))), + } + } +} + +#[cfg(feature = "tokio")] +impl<'a> AsyncWrite for Box { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let this = self.get_mut(); + let handler: Box = cx.waker().into(); + if let Err(err) = this.set_handler(handler) { + return Poll::Ready(Err(net_error_into_io_err(err))); + } + match this.try_send(buf) { + Ok(ret) => Poll::Ready(Ok(ret)), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(net_error_into_io_err(err))), + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + let handler: Box = cx.waker().into(); + if let Err(err) = this.set_handler(handler) { + return Poll::Ready(Err(net_error_into_io_err(err))); + } + match this.try_flush() { + Ok(()) => Poll::Ready(Ok(())), + Err(NetworkError::WouldBlock) => Poll::Pending, + Err(err) => Poll::Ready(Err(net_error_into_io_err(err))), + } + } + + fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready( + self.shutdown(Shutdown::Write) + .map_err(net_error_into_io_err), + ) + } +} + pub trait VirtualUdpSocket: VirtualConnectionlessSocket + fmt::Debug + Send + Sync + 'static { @@ -406,7 +717,7 @@ pub struct UnsupportedVirtualNetworking {} #[async_trait::async_trait] impl VirtualNetworking for UnsupportedVirtualNetworking {} -#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Error, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum NetworkError { /// The handle given was not usable #[error("invalid fd")] @@ -483,5 +794,93 @@ pub enum NetworkError { UnknownError, } -#[cfg(feature = "host-net")] -pub mod host; +pub fn io_err_into_net_error(net_error: std::io::Error) -> NetworkError { + use std::io::ErrorKind; + match net_error.kind() { + ErrorKind::BrokenPipe => NetworkError::BrokenPipe, + ErrorKind::AlreadyExists => NetworkError::AlreadyExists, + ErrorKind::AddrInUse => NetworkError::AddressInUse, + ErrorKind::AddrNotAvailable => NetworkError::AddressNotAvailable, + ErrorKind::ConnectionAborted => NetworkError::ConnectionAborted, + ErrorKind::ConnectionRefused => NetworkError::ConnectionRefused, + ErrorKind::ConnectionReset => NetworkError::ConnectionReset, + ErrorKind::Interrupted => NetworkError::Interrupted, + ErrorKind::InvalidData => NetworkError::InvalidData, + ErrorKind::InvalidInput => NetworkError::InvalidInput, + ErrorKind::NotConnected => NetworkError::NotConnected, + ErrorKind::PermissionDenied => NetworkError::PermissionDenied, + ErrorKind::TimedOut => NetworkError::TimedOut, + ErrorKind::UnexpectedEof => NetworkError::UnexpectedEof, + ErrorKind::WouldBlock => NetworkError::WouldBlock, + ErrorKind::WriteZero => NetworkError::WriteZero, + ErrorKind::Unsupported => NetworkError::Unsupported, + + #[cfg(all(target_family = "unix", feature = "libc"))] + _ => { + if let Some(code) = net_error.raw_os_error() { + match code { + libc::EPERM => NetworkError::PermissionDenied, + libc::EBADF => NetworkError::InvalidFd, + libc::ECHILD => NetworkError::InvalidFd, + libc::EMFILE => NetworkError::TooManyOpenFiles, + libc::EINTR => NetworkError::Interrupted, + libc::EIO => NetworkError::IOError, + libc::ENXIO => NetworkError::IOError, + libc::EAGAIN => NetworkError::WouldBlock, + libc::ENOMEM => NetworkError::InsufficientMemory, + libc::EACCES => NetworkError::PermissionDenied, + libc::ENODEV => NetworkError::NoDevice, + libc::EINVAL => NetworkError::InvalidInput, + libc::EPIPE => NetworkError::BrokenPipe, + err => { + tracing::trace!("unknown os error {}", err); + NetworkError::UnknownError + } + } + } else { + NetworkError::UnknownError + } + } + #[cfg(not(all(target_family = "unix", feature = "libc")))] + _ => NetworkError::UnknownError, + } +} + +pub fn net_error_into_io_err(net_error: NetworkError) -> std::io::Error { + use std::io::ErrorKind; + match net_error { + NetworkError::InvalidFd => ErrorKind::BrokenPipe.into(), + NetworkError::AlreadyExists => ErrorKind::AlreadyExists.into(), + NetworkError::Lock => ErrorKind::BrokenPipe.into(), + NetworkError::IOError => ErrorKind::BrokenPipe.into(), + NetworkError::AddressInUse => ErrorKind::AddrInUse.into(), + NetworkError::AddressNotAvailable => ErrorKind::AddrNotAvailable.into(), + NetworkError::BrokenPipe => ErrorKind::BrokenPipe.into(), + NetworkError::ConnectionAborted => ErrorKind::ConnectionAborted.into(), + NetworkError::ConnectionRefused => ErrorKind::ConnectionRefused.into(), + NetworkError::ConnectionReset => ErrorKind::ConnectionReset.into(), + NetworkError::Interrupted => ErrorKind::Interrupted.into(), + NetworkError::InvalidData => ErrorKind::InvalidData.into(), + NetworkError::InvalidInput => ErrorKind::InvalidInput.into(), + NetworkError::NotConnected => ErrorKind::NotConnected.into(), + NetworkError::NoDevice => ErrorKind::BrokenPipe.into(), + NetworkError::PermissionDenied => ErrorKind::PermissionDenied.into(), + NetworkError::TimedOut => ErrorKind::TimedOut.into(), + NetworkError::UnexpectedEof => ErrorKind::UnexpectedEof.into(), + NetworkError::WouldBlock => ErrorKind::WouldBlock.into(), + NetworkError::WriteZero => ErrorKind::WriteZero.into(), + NetworkError::Unsupported => ErrorKind::Unsupported.into(), + NetworkError::UnknownError => ErrorKind::BrokenPipe.into(), + NetworkError::InsufficientMemory => ErrorKind::OutOfMemory.into(), + NetworkError::TooManyOpenFiles => { + #[cfg(all(target_family = "unix", feature = "libc"))] + { + std::io::Error::from_raw_os_error(libc::EMFILE) + } + #[cfg(not(all(target_family = "unix", feature = "libc")))] + { + ErrorKind::Other.into() + } + } + } +} diff --git a/lib/virtual-net/src/meta.rs b/lib/virtual-net/src/meta.rs new file mode 100644 index 00000000000..c275254eeb8 --- /dev/null +++ b/lib/virtual-net/src/meta.rs @@ -0,0 +1,323 @@ +use serde::{Deserialize, Serialize}; + +pub use super::Duration; +pub use super::IpAddr; +pub use super::IpCidr; +pub use super::IpRoute; +pub use super::Ipv4Addr; +pub use super::Ipv6Addr; +pub use super::NetworkError; +pub use super::SocketAddr; +pub use super::SocketStatus; +pub use super::StreamSecurity; + +/// Represents a socket ID +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SocketId(u64); + +impl From for SocketId { + fn from(value: u64) -> Self { + Self(value) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub enum FrameSerializationFormat { + Bincode, + #[cfg(feature = "json")] + Json, + #[cfg(feature = "messagepack")] + MessagePack, + #[cfg(feature = "cbor")] + Cbor, +} + +/// Possible values which can be passed to the [`TcpStream::shutdown`] method. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub enum Shutdown { + /// The reading portion of the [`TcpStream`] should be shut down. + Read, + /// The writing portion of the [`TcpStream`] should be shut down. + Write, + /// Both the reading and the writing portions of the [`TcpStream`] should be shut down. + Both, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum RequestType { + /// Bridges this local network with a remote network, which is required in + /// order to make lower level networking calls (such as UDP/TCP) + Bridge { + network: String, + access_token: String, + security: StreamSecurity, + }, + /// Flushes all the data by ensuring a full round trip is completed + Flush, + /// Disconnects from the remote network essentially unbridging it + Unbridge, + /// Acquires an IP address on the network and configures the routing tables + DhcpAcquire, + /// Adds a static IP address to the interface with a netmask prefix + IpAdd { ip: IpAddr, prefix: u8 }, + /// Removes a static (or dynamic) IP address from the interface + IpRemove(IpAddr), + /// Clears all the assigned IP addresses for this interface + IpClear, + /// Lists all the IP addresses currently assigned to this interface + GetIpList, + /// Returns the hardware MAC address for this interface + GetMac, + /// Adds a default gateway to the routing table + GatewaySet(IpAddr), + /// Adds a specific route to the routing table + RouteAdd { + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + }, + /// Removes a routing rule from the routing table + RouteRemove(IpAddr), + /// Clears the routing table for this interface + RouteClear, + /// Lists all the routes defined in the routing table for this interface + GetRouteList, + /// Creates a low level socket that can read and write Ethernet packets + /// directly to the interface + BindRaw(SocketId), + /// Lists for TCP connections on a specific IP and Port combination + /// Multiple servers (processes or threads) can bind to the same port if they each set + /// the reuse-port and-or reuse-addr flags + ListenTcp { + socket_id: SocketId, + addr: SocketAddr, + only_v6: bool, + reuse_port: bool, + reuse_addr: bool, + }, + /// Opens a UDP socket that listens on a specific IP and Port combination + /// Multiple servers (processes or threads) can bind to the same port if they each set + /// the reuse-port and-or reuse-addr flags + BindUdp { + socket_id: SocketId, + addr: SocketAddr, + reuse_port: bool, + reuse_addr: bool, + }, + /// Creates a socket that can be used to send and receive ICMP packets + /// from a paritcular IP address + BindIcmp { socket_id: SocketId, addr: IpAddr }, + /// Opens a TCP connection to a particular destination IP address and port + ConnectTcp { + socket_id: SocketId, + addr: SocketAddr, + peer: SocketAddr, + }, + /// Performs DNS resolution for a specific hostname + Resolve { + host: String, + port: Option, + dns_server: Option, + }, + /// Closes the socket + Close, + /// Begins the process of accepting a socket and returns it later + BeginAccept(SocketId), + /// Returns the local address of this TCP listener + GetAddrLocal, + /// Returns the address (IP and Port) of the peer socket that this + /// is conencted to + GetAddrPeer, + /// Sets how many network hops the packets are permitted for new connections + SetTtl(u32), + /// Returns the maximum number of network hops before packets are dropped + GetTtl, + /// Returns the status/state of the socket + GetStatus, + /// Determines how long the socket will remain in a TIME_WAIT + /// after it disconnects (only the one that initiates the close will + /// be in a TIME_WAIT state thus the clients should always do this rather + /// than the server) + SetLinger(Option), + /// Returns how long the socket will remain in a TIME_WAIT + /// after it disconnects + GetLinger, + /// Tells the raw socket and its backing switch that all packets + /// should be received by this socket even if they are not + /// destined for this device + SetPromiscuous(bool), + /// Returns if the socket is running in promiscuous mode whereby it + /// will receive all packets even if they are not destined for the + /// local interface + GetPromiscuous, + /// Sets the receive buffer size which acts as a trottle for how + /// much data is buffered on this side of the pipe + SetRecvBufSize(u64), + /// Size of the receive buffer that holds all data that has not + /// yet been read + GetRecvBufSize, + /// Sets the size of the send buffer which will hold the bytes of + /// data while they are being sent over to the peer + SetSendBufSize(u64), + /// Size of the send buffer that holds all data that is currently + /// being transmitted. + GetSendBufSize, + /// When NO_DELAY is set the data that needs to be transmitted to + /// the peer is sent immediately rather than waiting for a bigger + /// batch of data, this reduces latency but increases encapsulation + /// overhead. + SetNoDelay(bool), + /// Indicates if the NO_DELAY flag is set which means that data + /// is immediately sent to the peer without waiting. This reduces + /// latency but increases encapsulation overhead. + GetNoDelay, + /// Shuts down either the READER or WRITER sides of the socket + /// connection. + Shutdown(Shutdown), + /// Return true if the socket is closed + IsClosed, + /// Sets a flag that means that the UDP socket is able + /// to receive and process broadcast packets. + SetBroadcast(bool), + /// Indicates if the SO_BROADCAST flag is set which means + /// that the UDP socket will receive and process broadcast + /// packets + GetBroadcast, + /// Sets a flag that indicates if multicast packets that + /// this socket is a member of will be looped back to + /// the sending socket. This applies to IPv4 addresses + SetMulticastLoopV4(bool), + /// Gets a flag that indicates if multicast packets that + /// this socket is a member of will be looped back to + /// the sending socket. This applies to IPv4 addresses + GetMulticastLoopV4, + /// Sets a flag that indicates if multicast packets that + /// this socket is a member of will be looped back to + /// the sending socket. This applies to IPv6 addresses + SetMulticastLoopV6(bool), + /// Gets a flag that indicates if multicast packets that + /// this socket is a member of will be looped back to + /// the sending socket. This applies to IPv6 addresses + GetMulticastLoopV6, + /// Sets the TTL for IPv4 multicast packets which is the + /// number of network hops before the packet is dropped + SetMulticastTtlV4(u32), + /// Gets the TTL for IPv4 multicast packets which is the + /// number of network hops before the packet is dropped + GetMulticastTtlV4, + /// Tells this interface that it will subscribe to a + /// particular multicast address. This applies to IPv4 addresses + JoinMulticastV4 { + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + }, + /// Tells this interface that it will unsubscribe to a + /// particular multicast address. This applies to IPv4 addresses + LeaveMulticastV4 { + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + }, + /// Tells this interface that it will subscribe to a + /// particular multicast address. This applies to IPv6 addresses + JoinMulticastV6 { multiaddr: Ipv6Addr, iface: u32 }, + /// Tells this interface that it will unsubscribe to a + /// particular multicast address. This applies to IPv6 addresses + LeaveMulticastV6 { multiaddr: Ipv6Addr, iface: u32 }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ResponseType { + /// Nothing is returned (or noop) + None, + /// An error has occurred + Err(NetworkError), + /// Represents a duration of time + Duration(Duration), + /// Represents an amount (e.g. amount of bytes) + Amount(u64), + /// Returns a flag of true or false + Flag(bool), + /// List of IP addresses + IpAddressList(Vec), + /// A single IP address + IpAddress(IpAddr), + /// List of socket addresses + SocketAddrList(Vec), + /// A single IP address + SocketAddr(SocketAddr), + /// Represents a MAC address + Mac([u8; 6]), + /// List of CIDR routes from a routing table + CidrList(Vec), + /// List of IP routes from a routing table + RouteList(Vec), + /// Reference to a socket + Socket(SocketId), + /// The TTL of a packet + Ttl(u32), + /// The status of the socket + Status(SocketStatus), +} + +/// Message sent by the client to the server +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum MessageRequest { + Interface { + req: RequestType, + req_id: Option, + }, + Socket { + socket: SocketId, + req: RequestType, + req_id: Option, + }, + Send { + socket: SocketId, + data: Vec, + req_id: Option, + }, + SendTo { + socket: SocketId, + data: Vec, + addr: SocketAddr, + req_id: Option, + }, + Reconnect, +} + +/// Message sent by the server back to a client +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum MessageResponse { + ResponseToRequest { + req_id: u64, + res: ResponseType, + }, + Recv { + socket_id: SocketId, + data: Vec, + }, + RecvWithAddr { + socket_id: SocketId, + data: Vec, + addr: SocketAddr, + }, + Sent { + socket_id: SocketId, + req_id: u64, + amount: u64, + }, + SendError { + socket_id: SocketId, + req_id: u64, + error: NetworkError, + }, + FinishAccept { + socket_id: SocketId, + child_id: SocketId, + addr: SocketAddr, + }, + Closed { + socket_id: SocketId, + }, +} diff --git a/lib/virtual-net/src/rx_tx.rs b/lib/virtual-net/src/rx_tx.rs new file mode 100644 index 00000000000..bb052f6de1d --- /dev/null +++ b/lib/virtual-net/src/rx_tx.rs @@ -0,0 +1,617 @@ +use std::{ + io, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +use crate::Result; +use futures_util::{future::BoxFuture, Future, Sink, SinkExt, Stream}; +use serde::Serialize; +#[cfg(feature = "tokio-tungstenite")] +use tokio::net::TcpStream; +use tokio::{ + io::AsyncWrite, + sync::{ + mpsc::{self, error::TrySendError}, + oneshot, + }, +}; +use virtual_mio::InlineWaker; + +use crate::{io_err_into_net_error, NetworkError}; + +#[derive(Debug, Clone, Default)] +pub(crate) struct RemoteTxWakers { + wakers: Arc>>, +} +impl RemoteTxWakers { + pub fn add(&self, waker: &Waker) { + let mut guard = self.wakers.lock().unwrap(); + if !guard.iter().any(|w| w.will_wake(waker)) { + guard.push(waker.clone()); + } + } + pub fn wake(&self) { + let mut guard = self.wakers.lock().unwrap(); + guard.drain(..).for_each(|w| w.wake()); + } +} + +#[derive(Debug, Default)] +struct FailOnWrite {} +impl AsyncWrite for FailOnWrite { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())) + } +} + +pub(crate) type StreamSink = Pin + Send + 'static>>; + +pub(crate) enum RemoteTx +where + T: Serialize, +{ + Mpsc { + tx: mpsc::Sender, + work: mpsc::UnboundedSender>, + wakers: RemoteTxWakers, + }, + Stream { + tx: Arc>>, + work: mpsc::UnboundedSender>, + wakers: RemoteTxWakers, + }, + #[cfg(feature = "hyper")] + HyperWebSocket { + tx: Arc< + tokio::sync::Mutex< + futures_util::stream::SplitSink< + hyper_tungstenite::WebSocketStream, + hyper_tungstenite::tungstenite::Message, + >, + >, + >, + work: mpsc::UnboundedSender>, + wakers: RemoteTxWakers, + format: crate::meta::FrameSerializationFormat, + }, + #[cfg(feature = "tokio-tungstenite")] + TokioWebSocket { + tx: Arc< + tokio::sync::Mutex< + futures_util::stream::SplitSink< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + work: mpsc::UnboundedSender>, + wakers: RemoteTxWakers, + format: crate::meta::FrameSerializationFormat, + }, +} +impl RemoteTx +where + T: Serialize + Send + Sync + 'static, +{ + pub(crate) async fn send(&self, req: T) -> Result<()> { + match self { + RemoteTx::Mpsc { tx, .. } => tx + .send(req) + .await + .map_err(|_| NetworkError::ConnectionAborted), + RemoteTx::Stream { tx, work, .. } => { + let (tx_done, rx_done) = oneshot::channel(); + let tx = tx.clone(); + work.send(Box::pin(async move { + let job = async { + let mut tx_guard = tx.lock_owned().await; + tx_guard.send(req).await.map_err(io_err_into_net_error) + }; + tx_done.send(job.await).ok(); + })) + .map_err(|_| NetworkError::ConnectionAborted)?; + + rx_done + .await + .unwrap_or(Err(NetworkError::ConnectionAborted)) + } + #[cfg(feature = "hyper")] + RemoteTx::HyperWebSocket { tx, format, .. } => { + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Err(NetworkError::IOError); + } + }; + let mut tx = tx.lock().await; + tx.send(hyper_tungstenite::tungstenite::Message::Binary(data)) + .await + .map_err(|_| NetworkError::ConnectionAborted) + } + #[cfg(feature = "tokio-tungstenite")] + RemoteTx::TokioWebSocket { tx, format, .. } => { + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Err(NetworkError::IOError); + } + }; + let mut tx = tx.lock().await; + tx.send(tokio_tungstenite::tungstenite::Message::Binary(data)) + .await + .map_err(|_| NetworkError::ConnectionAborted) + } + } + } + + pub(crate) fn poll_send(&self, cx: &mut Context<'_>, req: T) -> Poll> { + match self { + RemoteTx::Mpsc { tx, wakers, .. } => match tx.try_send(req) { + Ok(()) => Poll::Ready(Ok(())), + Err(TrySendError::Closed(_)) => Poll::Ready(Err(NetworkError::ConnectionAborted)), + Err(TrySendError::Full(_)) => { + wakers.add(cx.waker()); + Poll::Pending + } + }, + RemoteTx::Stream { tx, work, wakers } => { + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + wakers.add(cx.waker()); + return Poll::Pending; + } + }; + match tx_guard.poll_ready_unpin(cx) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(err)) => return Poll::Ready(Err(io_err_into_net_error(err))), + Poll::Pending => return Poll::Pending, + } + let mut job = Box::pin(async move { + if let Err(err) = tx_guard.send(req).await.map_err(io_err_into_net_error) { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(cx).is_ready() { + return Poll::Ready(Ok(())); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Poll::Ready(Ok(())) + } + #[cfg(feature = "hyper")] + RemoteTx::HyperWebSocket { + tx, + format, + work, + wakers, + .. + } => { + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + wakers.add(cx.waker()); + return Poll::Pending; + } + }; + match tx_guard.poll_ready_unpin(cx) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(err)) => { + tracing::warn!("failed to poll web socket for readiness - {err}"); + return Poll::Ready(Err(NetworkError::IOError)); + } + Poll::Pending => return Poll::Pending, + } + + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Poll::Ready(Err(NetworkError::IOError)); + } + }; + + let mut job = Box::pin(async move { + if let Err(err) = tx_guard + .send(hyper_tungstenite::tungstenite::Message::Binary(data)) + .await + { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(cx).is_ready() { + return Poll::Ready(Ok(())); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Poll::Ready(Ok(())) + } + #[cfg(feature = "tokio-tungstenite")] + RemoteTx::TokioWebSocket { + tx, + format, + work, + wakers, + .. + } => { + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + wakers.add(cx.waker()); + return Poll::Pending; + } + }; + match tx_guard.poll_ready_unpin(cx) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(err)) => { + tracing::warn!("failed to poll web socket for readiness - {err}"); + return Poll::Ready(Err(NetworkError::IOError)); + } + Poll::Pending => return Poll::Pending, + } + + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Poll::Ready(Err(NetworkError::IOError)); + } + }; + + let mut job = Box::pin(async move { + if let Err(err) = tx_guard + .send(tokio_tungstenite::tungstenite::Message::Binary(data)) + .await + { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(cx).is_ready() { + return Poll::Ready(Ok(())); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Poll::Ready(Ok(())) + } + } + } + + pub(crate) fn send_with_driver(&self, req: T) -> Result<()> { + match self { + RemoteTx::Mpsc { tx, work, .. } => match tx.try_send(req) { + Ok(()) => Ok(()), + Err(TrySendError::Closed(_)) => Err(NetworkError::ConnectionAborted), + Err(TrySendError::Full(req)) => { + let tx = tx.clone(); + work.send(Box::pin(async move { + tx.send(req).await.ok(); + })) + .ok(); + Ok(()) + } + }, + RemoteTx::Stream { tx, work, .. } => { + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + let tx = tx.clone(); + work.send(Box::pin(async move { + let mut tx_guard = tx.lock().await; + tx_guard.send(req).await.ok(); + })) + .ok(); + return Ok(()); + } + }; + + let inline_waker = InlineWaker::new(); + let waker = inline_waker.as_waker(); + let mut cx = Context::from_waker(&waker); + + let mut job = Box::pin(async move { + if let Err(err) = tx_guard.send(req).await.map_err(io_err_into_net_error) { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(&mut cx).is_ready() { + return Ok(()); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Ok(()) + } + #[cfg(feature = "hyper")] + RemoteTx::HyperWebSocket { + tx, format, work, .. + } => { + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Err(NetworkError::IOError); + } + }; + + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + let tx = tx.clone(); + work.send(Box::pin(async move { + let mut tx_guard = tx.lock().await; + tx_guard + .send(hyper_tungstenite::tungstenite::Message::Binary(data)) + .await + .ok(); + })) + .ok(); + return Ok(()); + } + }; + + let inline_waker = InlineWaker::new(); + let waker = inline_waker.as_waker(); + let mut cx = Context::from_waker(&waker); + + let mut job = Box::pin(async move { + if let Err(err) = tx_guard + .send(hyper_tungstenite::tungstenite::Message::Binary(data)) + .await + { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(&mut cx).is_ready() { + return Ok(()); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Ok(()) + } + #[cfg(feature = "tokio-tungstenite")] + RemoteTx::TokioWebSocket { + tx, format, work, .. + } => { + let data = match format { + crate::meta::FrameSerializationFormat::Bincode => bincode::serialize(&req) + .map_err(|err| { + tracing::warn!("failed to serialize message - {err}"); + NetworkError::IOError + })?, + format => { + tracing::warn!("format not currently supported - {format:?}"); + return Err(NetworkError::IOError); + } + }; + + let mut tx_guard = match tx.clone().try_lock_owned() { + Ok(lock) => lock, + Err(_) => { + let tx = tx.clone(); + work.send(Box::pin(async move { + let mut tx_guard = tx.lock().await; + tx_guard + .send(tokio_tungstenite::tungstenite::Message::Binary(data)) + .await + .ok(); + })) + .ok(); + return Ok(()); + } + }; + + let inline_waker = InlineWaker::new(); + let waker = inline_waker.as_waker(); + let mut cx = Context::from_waker(&waker); + + let mut job = Box::pin(async move { + if let Err(err) = tx_guard + .send(tokio_tungstenite::tungstenite::Message::Binary(data)) + .await + { + tracing::error!("failed to send remaining bytes for request - {}", err); + } + }); + + // First we try to finish it synchronously + if job.as_mut().poll(&mut cx).is_ready() { + return Ok(()); + } + + // Otherwise we push it to the driver which will block all future send + // operations until it finishes + work.send(job).map_err(|err| { + tracing::error!("failed to send remaining bytes for request - {}", err); + NetworkError::ConnectionAborted + })?; + Ok(()) + } + } + } +} + +pub(crate) enum RemoteRx +where + T: serde::de::DeserializeOwned, +{ + Mpsc { + rx: mpsc::Receiver, + wakers: RemoteTxWakers, + }, + Stream { + rx: Pin> + Send + 'static>>, + }, + #[cfg(feature = "hyper")] + HyperWebSocket { + rx: futures_util::stream::SplitStream< + hyper_tungstenite::WebSocketStream, + >, + format: crate::meta::FrameSerializationFormat, + }, + #[cfg(feature = "tokio-tungstenite")] + TokioWebSocket { + rx: futures_util::stream::SplitStream< + tokio_tungstenite::WebSocketStream>, + >, + format: crate::meta::FrameSerializationFormat, + }, +} +impl RemoteRx +where + T: serde::de::DeserializeOwned, +{ + pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { + loop { + return match self { + RemoteRx::Mpsc { rx, wakers } => { + let ret = Pin::new(rx).poll_recv(cx); + if ret.is_ready() { + wakers.wake(); + } + ret + } + RemoteRx::Stream { rx } => match rx.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(msg))) => Poll::Ready(Some(msg)), + Poll::Ready(Some(Err(err))) => { + tracing::debug!("failed to read from channel - {}", err); + Poll::Ready(None) + } + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }, + #[cfg(feature = "hyper")] + RemoteRx::HyperWebSocket { rx, format } => match Pin::new(rx).poll_next(cx) { + Poll::Ready(Some(Ok(hyper_tungstenite::tungstenite::Message::Binary(msg)))) => { + match format { + crate::meta::FrameSerializationFormat::Bincode => { + return match bincode::deserialize(&msg) { + Ok(msg) => Poll::Ready(Some(msg)), + Err(err) => { + tracing::warn!("failed to deserialize message - {}", err); + continue; + } + } + } + format => { + tracing::warn!("format not currently supported - {format:?}"); + continue; + } + } + } + Poll::Ready(Some(Ok(msg))) => { + tracing::warn!("unsupported message from channel - {}", msg); + continue; + } + Poll::Ready(Some(Err(err))) => { + tracing::debug!("failed to read from channel - {}", err); + Poll::Ready(None) + } + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }, + #[cfg(feature = "tokio-tungstenite")] + RemoteRx::TokioWebSocket { rx, format } => match Pin::new(rx).poll_next(cx) { + Poll::Ready(Some(Ok(tokio_tungstenite::tungstenite::Message::Binary(msg)))) => { + match format { + crate::meta::FrameSerializationFormat::Bincode => { + return match bincode::deserialize(&msg) { + Ok(msg) => Poll::Ready(Some(msg)), + Err(err) => { + tracing::warn!("failed to deserialize message - {}", err); + continue; + } + } + } + format => { + tracing::warn!("format not currently supported - {format:?}"); + continue; + } + } + } + Poll::Ready(Some(Ok(msg))) => { + tracing::warn!("unsupported message from channel - {}", msg); + continue; + } + Poll::Ready(Some(Err(err))) => { + tracing::debug!("failed to read from channel - {}", err); + Poll::Ready(None) + } + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }, + }; + } + } +} diff --git a/lib/virtual-net/src/server.rs b/lib/virtual-net/src/server.rs new file mode 100644 index 00000000000..840addac83a --- /dev/null +++ b/lib/virtual-net/src/server.rs @@ -0,0 +1,1577 @@ +use crate::meta::{FrameSerializationFormat, ResponseType}; +use crate::rx_tx::{RemoteRx, RemoteTx, RemoteTxWakers}; +use crate::{ + meta::{MessageRequest, MessageResponse, RequestType, SocketId}, + VirtualNetworking, VirtualRawSocket, VirtualTcpListener, VirtualTcpSocket, VirtualUdpSocket, +}; +use crate::{IpCidr, IpRoute, NetworkError, StreamSecurity, VirtualIcmpSocket}; +use derivative::Derivative; +use futures_util::stream::FuturesOrdered; +#[cfg(any(feature = "hyper", feature = "tokio-tungstenite"))] +use futures_util::stream::{SplitSink, SplitStream}; +use futures_util::{future::BoxFuture, StreamExt}; +use futures_util::{Sink, Stream}; +use std::collections::HashSet; +use std::mem::MaybeUninit; +use std::net::IpAddr; +use std::task::Waker; +use std::time::Duration; +use std::{ + collections::HashMap, + future::Future, + net::SocketAddr, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + sync::mpsc, +}; +use tokio_serde::formats::SymmetricalBincode; +#[cfg(feature = "cbor")] +use tokio_serde::formats::SymmetricalCbor; +#[cfg(feature = "json")] +use tokio_serde::formats::SymmetricalJson; +#[cfg(feature = "messagepack")] +use tokio_serde::formats::SymmetricalMessagePack; +use tokio_serde::SymmetricallyFramed; +use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; +use virtual_mio::InterestHandler; + +type BackgroundTask = Option>; + +#[derive(Debug, Clone)] +pub struct RemoteNetworkingServer { + #[allow(dead_code)] + common: Arc, + inner: Arc, +} + +impl RemoteNetworkingServer { + fn new( + tx: RemoteTx, + rx: RemoteRx, + work: mpsc::UnboundedReceiver>, + inner: Arc, + ) -> (Self, RemoteNetworkingServerDriver) { + let common = RemoteAdapterCommon { + tx, + rx: Mutex::new(rx), + sockets: Default::default(), + socket_accept: Default::default(), + handler: Default::default(), + stall_rx: Default::default(), + }; + let common = Arc::new(common); + + let driver = RemoteNetworkingServerDriver { + more_work: work, + tasks: Default::default(), + common: common.clone(), + inner: inner.clone(), + }; + let networking = Self { common, inner }; + + (networking, driver) + } + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + pub fn new_from_mpsc( + tx: mpsc::Sender, + rx: mpsc::Receiver, + inner: Arc, + ) -> (Self, RemoteNetworkingServerDriver) { + let (tx_work, rx_work) = mpsc::unbounded_channel(); + let tx_wakers = RemoteTxWakers::default(); + + let tx = RemoteTx::Mpsc { + tx, + work: tx_work, + wakers: tx_wakers.clone(), + }; + let rx = RemoteRx::Mpsc { + rx, + wakers: tx_wakers, + }; + Self::new(tx, rx, rx_work, inner) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + pub fn new_from_async_io( + tx: TX, + rx: RX, + format: FrameSerializationFormat, + inner: Arc, + ) -> (Self, RemoteNetworkingServerDriver) + where + TX: AsyncWrite + Send + 'static, + RX: AsyncRead + Send + 'static, + { + let tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); + let tx: Pin + Send + 'static>> = + match format { + FrameSerializationFormat::Bincode => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalBincode::default())) + } + #[cfg(feature = "json")] + FrameSerializationFormat::Json => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalJson::default())) + } + #[cfg(feature = "messagepack")] + FrameSerializationFormat::MessagePack => Box::pin(SymmetricallyFramed::new( + tx, + SymmetricalMessagePack::default(), + )), + #[cfg(feature = "cbor")] + FrameSerializationFormat::Cbor => { + Box::pin(SymmetricallyFramed::new(tx, SymmetricalCbor::default())) + } + }; + + let rx = FramedRead::new(rx, LengthDelimitedCodec::new()); + let rx: Pin> + Send + 'static>> = + match format { + FrameSerializationFormat::Bincode => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalBincode::default())) + } + #[cfg(feature = "json")] + FrameSerializationFormat::Json => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalJson::default())) + } + #[cfg(feature = "messagepack")] + FrameSerializationFormat::MessagePack => Box::pin(SymmetricallyFramed::new( + rx, + SymmetricalMessagePack::default(), + )), + #[cfg(feature = "cbor")] + FrameSerializationFormat::Cbor => { + Box::pin(SymmetricallyFramed::new(rx, SymmetricalCbor::default())) + } + }; + + let (tx_work, rx_work) = mpsc::unbounded_channel(); + + let tx = RemoteTx::Stream { + tx: Arc::new(tokio::sync::Mutex::new(tx)), + work: tx_work, + wakers: RemoteTxWakers::default(), + }; + let rx = RemoteRx::Stream { rx }; + Self::new(tx, rx, rx_work, inner) + } + + /// Creates a new interface on the remote location using + /// a unique interface ID and a pair of channels + #[cfg(feature = "hyper")] + pub fn new_from_hyper_ws_io( + tx: SplitSink< + hyper_tungstenite::WebSocketStream, + hyper_tungstenite::tungstenite::Message, + >, + rx: SplitStream>, + format: FrameSerializationFormat, + inner: Arc, + ) -> (Self, RemoteNetworkingServerDriver) { + let (tx_work, rx_work) = mpsc::unbounded_channel(); + + let tx = RemoteTx::HyperWebSocket { + tx: Arc::new(tokio::sync::Mutex::new(tx)), + work: tx_work, + wakers: RemoteTxWakers::default(), + format, + }; + let rx = RemoteRx::HyperWebSocket { rx, format }; + Self::new(tx, rx, rx_work, inner) + } +} + +#[async_trait::async_trait] +impl VirtualNetworking for RemoteNetworkingServer { + async fn bridge( + &self, + network: &str, + access_token: &str, + security: StreamSecurity, + ) -> Result<(), NetworkError> { + self.inner.bridge(network, access_token, security).await + } + + async fn unbridge(&self) -> Result<(), NetworkError> { + self.inner.unbridge().await + } + + async fn dhcp_acquire(&self) -> Result, NetworkError> { + self.inner.dhcp_acquire().await + } + + async fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<(), NetworkError> { + self.inner.ip_add(ip, prefix).await + } + + async fn ip_remove(&self, ip: IpAddr) -> Result<(), NetworkError> { + self.inner.ip_remove(ip).await + } + + async fn ip_clear(&self) -> Result<(), NetworkError> { + self.inner.ip_clear().await + } + + async fn ip_list(&self) -> Result, NetworkError> { + self.inner.ip_list().await + } + + async fn mac(&self) -> Result<[u8; 6], NetworkError> { + self.inner.mac().await + } + + async fn gateway_set(&self, ip: IpAddr) -> Result<(), NetworkError> { + self.inner.gateway_set(ip).await + } + + async fn route_add( + &self, + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + ) -> Result<(), NetworkError> { + self.inner + .route_add(cidr, via_router, preferred_until, expires_at) + .await + } + + async fn route_remove(&self, cidr: IpAddr) -> Result<(), NetworkError> { + self.inner.route_remove(cidr).await + } + + async fn route_clear(&self) -> Result<(), NetworkError> { + self.inner.route_clear().await + } + + async fn route_list(&self) -> Result, NetworkError> { + self.inner.route_list().await + } + + async fn bind_raw(&self) -> Result, NetworkError> { + self.inner.bind_raw().await + } + + async fn listen_tcp( + &self, + addr: SocketAddr, + only_v6: bool, + reuse_port: bool, + reuse_addr: bool, + ) -> Result, NetworkError> { + self.inner + .listen_tcp(addr, only_v6, reuse_port, reuse_addr) + .await + } + + async fn bind_udp( + &self, + addr: SocketAddr, + reuse_port: bool, + reuse_addr: bool, + ) -> Result, NetworkError> { + self.inner.bind_udp(addr, reuse_port, reuse_addr).await + } + + async fn bind_icmp( + &self, + addr: IpAddr, + ) -> Result, NetworkError> { + self.inner.bind_icmp(addr).await + } + + async fn connect_tcp( + &self, + addr: SocketAddr, + peer: SocketAddr, + ) -> Result, NetworkError> { + self.inner.connect_tcp(addr, peer).await + } + + async fn resolve( + &self, + host: &str, + port: Option, + dns_server: Option, + ) -> Result, NetworkError> { + self.inner.resolve(host, port, dns_server).await + } +} + +pin_project_lite::pin_project! { + pub struct RemoteNetworkingServerDriver { + common: Arc, + more_work: mpsc::UnboundedReceiver>, + #[pin] + tasks: FuturesOrdered>, + inner: Arc, + } +} + +impl Future for RemoteNetworkingServerDriver { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We register the waker into the interest of the sockets so + // that it is woken when something is ready to read or write + let readable = { + let mut guard = self.common.handler.state.lock().unwrap(); + if !guard.driver_wakers.iter().any(|w| w.will_wake(cx.waker())) { + guard.driver_wakers.push(cx.waker().clone()); + } + guard.readable.drain().collect() + }; + let readable: Vec<_> = readable; + + { + // When a socket is marked as readable then we should drain all the data + // from it and start sending it to the client + let common = self.common.clone(); + let mut guard = common.sockets.lock().unwrap(); + for socket_id in readable { + if let Some(task) = guard + .get_mut(&socket_id) + .map(|s| s.drain_reads_and_accepts(&common, socket_id)) + .unwrap_or(None) + { + self.tasks.push_back(task); + } + } + } + + // This guard will be held while the pipeline is not currently + // stalled by some back pressure. It is only acquired when there + // is background tasks being processed + let mut not_stalled_guard = None; + + // We loop until the waker is registered with the receiving stream + // and all the background tasks + loop { + // Background tasks are sent to this driver in certain circumstances + while let Poll::Ready(Some(work)) = Pin::new(&mut self.more_work).poll_recv(cx) { + self.tasks.push_back(work); + } + + // Background work basically stalls the stream until its all processed + // which creates back pressure on the client so that they don't overload + // the system + match self.tasks.poll_next_unpin(cx) { + Poll::Ready(Some(_)) => continue, + Poll::Ready(None) => { + not_stalled_guard.take(); + } + Poll::Pending if not_stalled_guard.is_none() => { + if let Ok(guard) = self.common.stall_rx.clone().try_lock_owned() { + not_stalled_guard.replace(guard); + } else { + return Poll::Pending; + } + } + Poll::Pending => {} + }; + + // We grab the next message sent by the client to us + let msg = { + let mut rx_guard = self.common.rx.lock().unwrap(); + rx_guard.poll(cx) + }; + return match msg { + Poll::Ready(Some(msg)) => { + if let Some(task) = self.process(msg) { + // With some messages we process there are background tasks that need to + // be further driver to completion by the driver + self.tasks.push_back(task) + }; + continue; + } + Poll::Ready(None) => Poll::Ready(()), + Poll::Pending => Poll::Pending, + }; + } + } +} + +impl RemoteNetworkingServerDriver { + fn process(&mut self, msg: MessageRequest) -> BackgroundTask { + match msg { + MessageRequest::Send { + socket, + data, + req_id, + } => self.process_send(socket, data, req_id), + MessageRequest::SendTo { + socket, + data, + addr, + req_id, + } => self.process_send_to(socket, data, addr, req_id), + MessageRequest::Interface { req, req_id } => self.process_interface(req, req_id), + MessageRequest::Socket { + socket, + req, + req_id, + } => self.process_socket(socket, req, req_id), + MessageRequest::Reconnect => None, + } + } + + fn process_send( + &mut self, + socket_id: SocketId, + data: Vec, + req_id: Option, + ) -> BackgroundTask { + let mut guard = self.common.sockets.lock().unwrap(); + guard + .get_mut(&socket_id) + .map(|s| s.send(&self.common, socket_id, data, req_id)) + .unwrap_or_else(|| { + tracing::debug!("orphaned socket {:?}", socket_id); + None + }) + } + + fn process_send_to( + &mut self, + socket_id: SocketId, + data: Vec, + addr: SocketAddr, + req_id: Option, + ) -> BackgroundTask { + let mut guard = self.common.sockets.lock().unwrap(); + guard + .get_mut(&socket_id) + .map(|s| { + req_id.and_then(|req_id| s.send_to(&self.common, socket_id, data, addr, req_id)) + }) + .unwrap_or(None) + } + + fn process_async(future: F) -> BackgroundTask + where + F: Future + Send + 'static, + { + Some(Box::pin(async move { + let background_task = future.await; + if let Some(background_task) = background_task { + background_task.await; + } + })) + } + + fn process_async_inner( + &self, + work: F, + transmute: T, + req_id: Option, + ) -> BackgroundTask + where + F: FnOnce(Arc) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + T: FnOnce(Fut::Output) -> ResponseType + Send + 'static, + { + let inner = self.inner.clone(); + let common = self.common.clone(); + Self::process_async(async move { + let future = work(inner); + let ret = future.await; + req_id.and_then(|req_id| { + common.send(MessageResponse::ResponseToRequest { + req_id, + res: transmute(ret), + }) + }) + }) + } + + fn process_async_noop(&self, work: F, req_id: Option) -> BackgroundTask + where + F: FnOnce(Arc) -> Fut + Send + 'static, + Fut: Future> + Send + 'static, + { + self.process_async_inner( + work, + move |ret| match ret { + Ok(()) => ResponseType::None, + Err(err) => ResponseType::Err(err), + }, + req_id, + ) + } + + fn process_async_new_socket( + &self, + work: F, + socket_id: SocketId, + req_id: Option, + ) -> BackgroundTask + where + F: FnOnce(Arc) -> Fut + Send + 'static, + Fut: Future> + Send + 'static, + { + let common = self.common.clone(); + self.process_async_inner( + work, + move |ret| match ret { + Ok(mut socket) => { + let handler = Box::new(common.handler.clone().for_socket(socket_id)); + + let err = match &mut socket { + RemoteAdapterSocket::TcpListener { .. } => { + // we do not attach the handler immediately with new TPC listeners as we + // only want it to trigger when the BeginAccept message is received with + // a child ID we can actually use + Ok(()) + } + RemoteAdapterSocket::TcpSocket(s) => s.set_handler(handler), + RemoteAdapterSocket::UdpSocket(s) => s.set_handler(handler), + RemoteAdapterSocket::IcmpSocket(s) => s.set_handler(handler), + RemoteAdapterSocket::RawSocket(s) => s.set_handler(handler), + }; + if let Err(err) = err { + return ResponseType::Err(err); + } + + let mut guard = common.sockets.lock().unwrap(); + guard.insert(socket_id, socket); + + ResponseType::Socket(socket_id) + } + Err(err) => ResponseType::Err(err), + }, + req_id, + ) + } + + fn process_inner( + &self, + work: F, + transmute: T, + socket_id: SocketId, + req_id: Option, + ) -> BackgroundTask + where + F: FnOnce(&mut RemoteAdapterSocket) -> R + Send + 'static, + T: FnOnce(R) -> ResponseType + Send + 'static, + { + let ret = { + let mut guard = self.common.sockets.lock().unwrap(); + let socket = match guard.get_mut(&socket_id) { + Some(s) => s, + None => { + return req_id.and_then(|req_id| { + self.common.send(MessageResponse::ResponseToRequest { + req_id, + res: ResponseType::Err(NetworkError::InvalidFd), + }) + }) + } + }; + work(socket) + }; + req_id.and_then(|req_id| { + self.common.send(MessageResponse::ResponseToRequest { + req_id, + res: transmute(ret), + }) + }) + } + + fn process_inner_noop( + &self, + work: F, + socket_id: SocketId, + req_id: Option, + ) -> BackgroundTask + where + F: FnOnce(&mut RemoteAdapterSocket) -> Result<(), NetworkError> + Send + 'static, + { + self.process_inner( + work, + move |ret| match ret { + Ok(()) => ResponseType::None, + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ) + } + + fn process_inner_begin_accept( + &self, + socket_id: SocketId, + child_id: SocketId, + req_id: Option, + ) -> BackgroundTask { + // We record the child socket so it can be used on the next accepted socket + { + let mut guard = self.common.socket_accept.lock().unwrap(); + guard.insert(socket_id, child_id); + } + + // Now we attach the handler to the main listening socket + let mut handler = Box::new(self.common.handler.clone().for_socket(socket_id)); + handler.interest(virtual_mio::InterestType::Readable); + self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpListener { + socket: s, + next_accept, + .. + } => { + next_accept.replace(child_id); + s.set_handler(handler) + } + _ => { + // only the TCP listener needs its socket set as the other sockets + // set their handlers when the socket is created instead. we need to + // delay setting the handler so we have a child ID to use and return + // to the client when a socket is accepted, thus we can not accept them + // immediately + Err(NetworkError::Unsupported) + } + }, + socket_id, + req_id, + ) + } + + fn process_interface(&mut self, req: RequestType, req_id: Option) -> BackgroundTask { + match req { + RequestType::Bridge { + network, + access_token, + security, + } => self.process_async_noop( + move |inner| async move { inner.bridge(&network, &access_token, security).await }, + req_id, + ), + RequestType::Unbridge => { + self.process_async_noop(move |inner| async move { inner.unbridge().await }, req_id) + } + RequestType::DhcpAcquire => self.process_async_inner( + move |inner| async move { inner.dhcp_acquire().await }, + |ret| match ret { + Ok(ips) => ResponseType::IpAddressList(ips), + Err(err) => ResponseType::Err(err), + }, + req_id, + ), + RequestType::IpAdd { ip, prefix } => self.process_async_noop( + move |inner: Arc| async move { + inner.ip_add(ip, prefix).await + }, + req_id, + ), + RequestType::IpRemove(ip) => self.process_async_noop( + move |inner: Arc| async move { + inner.ip_remove(ip).await + }, + req_id, + ), + RequestType::IpClear => self.process_async_noop( + move |inner: Arc| async move { + inner.ip_clear().await + }, + req_id, + ), + RequestType::GetIpList => self.process_async_inner( + move |inner: Arc| async move { + inner.ip_list().await + }, + |ret| match ret { + Ok(cidr) => ResponseType::CidrList(cidr), + Err(err) => ResponseType::Err(err), + }, + req_id, + ), + RequestType::GetMac => { + self.process_async_inner( + move |inner: Arc| async move { + inner.mac().await + }, + |ret| match ret { + Ok(mac) => ResponseType::Mac(mac), + Err(err) => ResponseType::Err(err), + }, + req_id, + ) + } + RequestType::GatewaySet(ip) => self.process_async_noop( + move |inner: Arc| async move { + inner.gateway_set(ip).await + }, + req_id, + ), + RequestType::RouteAdd { + cidr, + via_router, + preferred_until, + expires_at, + } => self.process_async_noop( + move |inner: Arc| async move { + inner + .route_add(cidr, via_router, preferred_until, expires_at) + .await + }, + req_id, + ), + RequestType::RouteRemove(ip) => self.process_async_noop( + move |inner: Arc| async move { + inner.route_remove(ip).await + }, + req_id, + ), + RequestType::RouteClear => self.process_async_noop( + move |inner: Arc| async move { + inner.route_clear().await + }, + req_id, + ), + RequestType::GetRouteList => self.process_async_inner( + move |inner: Arc| async move { + inner.route_list().await + }, + |ret| match ret { + Ok(routes) => ResponseType::RouteList(routes), + Err(err) => ResponseType::Err(err), + }, + req_id, + ), + RequestType::BindRaw(socket_id) => self.process_async_new_socket( + move |inner: Arc| async move { + Ok(RemoteAdapterSocket::RawSocket(inner.bind_raw().await?)) + }, + socket_id, + req_id, + ), + RequestType::ListenTcp { + socket_id, + addr, + only_v6, + reuse_port, + reuse_addr, + } => self.process_async_new_socket( + move |inner: Arc| async move { + Ok(RemoteAdapterSocket::TcpListener { + socket: inner + .listen_tcp(addr, only_v6, reuse_port, reuse_addr) + .await?, + next_accept: None, + }) + }, + socket_id, + req_id, + ), + RequestType::BindUdp { + socket_id, + addr, + reuse_port, + reuse_addr, + } => self.process_async_new_socket( + move |inner: Arc| async move { + Ok(RemoteAdapterSocket::UdpSocket( + inner.bind_udp(addr, reuse_port, reuse_addr).await?, + )) + }, + socket_id, + req_id, + ), + RequestType::BindIcmp { socket_id, addr } => self.process_async_new_socket( + move |inner: Arc| async move { + Ok(RemoteAdapterSocket::IcmpSocket( + inner.bind_icmp(addr).await?, + )) + }, + socket_id, + req_id, + ), + RequestType::ConnectTcp { + socket_id, + addr, + peer, + } => self.process_async_new_socket( + move |inner: Arc| async move { + Ok(RemoteAdapterSocket::TcpSocket( + inner.connect_tcp(addr, peer).await?, + )) + }, + socket_id, + req_id, + ), + RequestType::Resolve { + host, + port, + dns_server, + } => self.process_async_inner( + move |inner: Arc| async move { + inner.resolve(&host, port, dns_server).await + }, + |ret| match ret { + Ok(ips) => ResponseType::IpAddressList(ips), + Err(err) => ResponseType::Err(err), + }, + req_id, + ), + _ => req_id.and_then(|req_id| { + self.common.send(MessageResponse::ResponseToRequest { + req_id, + res: ResponseType::Err(NetworkError::Unsupported), + }) + }), + } + } + + fn process_socket( + &mut self, + socket_id: SocketId, + req: RequestType, + req_id: Option, + ) -> BackgroundTask { + match req { + RequestType::Flush => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.try_flush(), + RemoteAdapterSocket::RawSocket(s) => s.try_flush(), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::Close => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.close(), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::BeginAccept(child_id) => { + self.process_inner_begin_accept(socket_id, child_id, req_id) + } + RequestType::GetAddrLocal => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.addr_local(), + RemoteAdapterSocket::TcpListener { socket: s, .. } => s.addr_local(), + RemoteAdapterSocket::UdpSocket(s) => s.addr_local(), + RemoteAdapterSocket::IcmpSocket(s) => s.addr_local(), + RemoteAdapterSocket::RawSocket(s) => s.addr_local(), + }, + |ret| match ret { + Ok(addr) => ResponseType::SocketAddr(addr), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::GetAddrPeer => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.addr_peer().map(Some), + RemoteAdapterSocket::TcpListener { .. } => Err(NetworkError::Unsupported), + RemoteAdapterSocket::UdpSocket(s) => s.addr_peer(), + RemoteAdapterSocket::IcmpSocket(_) => Err(NetworkError::Unsupported), + RemoteAdapterSocket::RawSocket(_) => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(Some(addr)) => ResponseType::SocketAddr(addr), + Ok(None) => ResponseType::None, + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetTtl(ttl) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.set_ttl(ttl), + RemoteAdapterSocket::TcpListener { socket: s, .. } => { + s.set_ttl(ttl.try_into().unwrap_or_default()) + } + RemoteAdapterSocket::UdpSocket(s) => s.set_ttl(ttl), + RemoteAdapterSocket::IcmpSocket(s) => s.set_ttl(ttl), + RemoteAdapterSocket::RawSocket(s) => s.set_ttl(ttl), + }, + socket_id, + req_id, + ), + RequestType::GetTtl => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.ttl(), + RemoteAdapterSocket::TcpListener { socket: s, .. } => s.ttl().map(|t| t as u32), + RemoteAdapterSocket::UdpSocket(s) => s.ttl(), + RemoteAdapterSocket::IcmpSocket(s) => s.ttl(), + RemoteAdapterSocket::RawSocket(s) => s.ttl(), + }, + |ret| match ret { + Ok(ttl) => ResponseType::Ttl(ttl), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::GetStatus => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.status(), + RemoteAdapterSocket::TcpListener { .. } => Err(NetworkError::Unsupported), + RemoteAdapterSocket::UdpSocket(s) => s.status(), + RemoteAdapterSocket::IcmpSocket(s) => s.status(), + RemoteAdapterSocket::RawSocket(s) => s.status(), + }, + |ret| match ret { + Ok(status) => ResponseType::Status(status), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetLinger(linger) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.set_linger(linger), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetLinger => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.linger(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(Some(time)) => ResponseType::Duration(time), + Ok(None) => ResponseType::None, + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetPromiscuous(promiscuous) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::RawSocket(s) => s.set_promiscuous(promiscuous), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetPromiscuous => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::RawSocket(s) => s.promiscuous(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetRecvBufSize(size) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => { + s.set_recv_buf_size(size.try_into().unwrap_or_default()) + } + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetRecvBufSize => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.recv_buf_size(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(amt) => ResponseType::Amount(amt as u64), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetSendBufSize(size) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => { + s.set_send_buf_size(size.try_into().unwrap_or_default()) + } + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetSendBufSize => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.send_buf_size(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(amt) => ResponseType::Amount(amt as u64), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetNoDelay(reuse) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.set_nodelay(reuse), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetNoDelay => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.nodelay(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::Shutdown(shutdown) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => s.shutdown(match shutdown { + crate::meta::Shutdown::Read => std::net::Shutdown::Read, + crate::meta::Shutdown::Write => std::net::Shutdown::Write, + crate::meta::Shutdown::Both => std::net::Shutdown::Both, + }), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::IsClosed => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::TcpSocket(s) => Ok(s.is_closed()), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetBroadcast(broadcast) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.set_broadcast(broadcast), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetBroadcast => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.broadcast(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetMulticastLoopV4(val) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.set_multicast_loop_v4(val), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetMulticastLoopV4 => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.multicast_loop_v4(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetMulticastLoopV6(val) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.set_multicast_loop_v6(val), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetMulticastLoopV6 => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.multicast_loop_v6(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(flag) => ResponseType::Flag(flag), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::SetMulticastTtlV4(ttl) => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.set_multicast_ttl_v4(ttl), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::GetMulticastTtlV4 => self.process_inner( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.multicast_ttl_v4(), + _ => Err(NetworkError::Unsupported), + }, + |ret| match ret { + Ok(ttl) => ResponseType::Ttl(ttl), + Err(err) => ResponseType::Err(err), + }, + socket_id, + req_id, + ), + RequestType::JoinMulticastV4 { multiaddr, iface } => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.join_multicast_v4(multiaddr, iface), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::LeaveMulticastV4 { multiaddr, iface } => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.leave_multicast_v4(multiaddr, iface), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::JoinMulticastV6 { multiaddr, iface } => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.join_multicast_v6(multiaddr, iface), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + RequestType::LeaveMulticastV6 { multiaddr, iface } => self.process_inner_noop( + move |socket| match socket { + RemoteAdapterSocket::UdpSocket(s) => s.leave_multicast_v6(multiaddr, iface), + _ => Err(NetworkError::Unsupported), + }, + socket_id, + req_id, + ), + _ => req_id.and_then(|req_id| { + self.common.send(MessageResponse::ResponseToRequest { + req_id, + res: ResponseType::Err(NetworkError::Unsupported), + }) + }), + } + } +} + +enum RemoteAdapterSocket { + TcpListener { + socket: Box, + next_accept: Option, + }, + TcpSocket(Box), + UdpSocket(Box), + RawSocket(Box), + IcmpSocket(Box), +} + +impl RemoteAdapterSocket { + pub fn send( + &mut self, + common: &Arc, + socket_id: SocketId, + data: Vec, + req_id: Option, + ) -> BackgroundTask { + match self { + Self::TcpSocket(this) => match this.try_send(&data) { + Ok(amount) => { + if let Some(req_id) = req_id { + common.send(MessageResponse::Sent { + socket_id, + req_id, + amount: amount as u64, + }) + } else { + None + } + } + Err(NetworkError::WouldBlock) => { + let common = common.clone(); + Some(Box::pin(async move { + // We will stall the receiver so that back pressure is sent back to the + // sender and they don't overwhelm us with transmitting data. + let _stall_rx = common.stall_rx.clone().lock_owned().await; + + // We use a poller here that uses the handler to wake itself up + struct Poller { + common: Arc, + socket_id: SocketId, + data: Vec, + req_id: Option, + } + impl Future for Poller { + type Output = BackgroundTask; + fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll { + // We make sure the waker is registered with the interest driver which will + // wake up this poller when there is writeability + let mut guard = self.common.handler.state.lock().unwrap(); + if !guard.driver_wakers.iter().any(|w| w.will_wake(cx.waker())) { + guard.driver_wakers.push(cx.waker().clone()); + } + drop(guard); + + let mut guard = self.common.sockets.lock().unwrap(); + if let Some(RemoteAdapterSocket::TcpSocket(socket)) = + guard.get_mut(&self.socket_id) + { + match socket.try_send(&self.data) { + Ok(amount) => { + if let Some(req_id) = self.req_id { + return Poll::Ready(self.common.send( + MessageResponse::Sent { + socket_id: self.socket_id, + req_id, + amount: amount as u64, + }, + )); + } else { + return Poll::Ready(None); + } + } + Err(NetworkError::WouldBlock) => return Poll::Pending, + Err(error) => { + if let Some(req_id) = self.req_id { + return Poll::Ready(self.common.send( + MessageResponse::SendError { + socket_id: self.socket_id, + req_id, + error, + }, + )); + } else { + return Poll::Ready(None); + } + } + } + } + Poll::Ready(None) + } + } + + // Run the poller until this message is sent, or the socket fails + let background_task = Poller { + common, + socket_id, + data, + req_id, + } + .await; + + // There might be more work left to finish off the send operation + if let Some(background_task) = background_task { + background_task.await; + } + })) + } + Err(error) => { + if let Some(req_id) = req_id { + common.send(MessageResponse::SendError { + socket_id, + req_id, + error, + }) + } else { + None + } + } + }, + Self::RawSocket(this) => { + // when the RAW socket is overloaded we just silently drop the packet + // rather than buffering it and retrying later - Ethernet packets are + // not lossless. In reality most socket drivers under this remote socket + // will always succeed on `try_send` with RawSockets as they are always + // processed. + if let Err(err) = this.try_send(&data) { + tracing::debug!("failed to send raw packet - {}", err); + } + None + } + _ => { + if let Some(req_id) = req_id { + common.send(MessageResponse::SendError { + socket_id, + req_id, + error: NetworkError::Unsupported, + }) + } else { + None + } + } + } + } + pub fn send_to( + &mut self, + common: &Arc, + socket_id: SocketId, + data: Vec, + addr: SocketAddr, + req_id: u64, + ) -> BackgroundTask { + match self { + Self::UdpSocket(this) => { + // when the UDP socket is overloaded we just silently drop the packet + // rather than buffering it and retrying later + this.try_send_to(&data, addr).ok(); + None + } + + Self::IcmpSocket(this) => { + // when the ICMP socket is overloaded we just silently drop the packet + // rather than buffering it and retrying later + this.try_send_to(&data, addr).ok(); + None + } + _ => common.send(MessageResponse::SendError { + socket_id, + req_id, + error: NetworkError::Unsupported, + }), + } + } + pub fn drain_reads_and_accepts( + &mut self, + common: &Arc, + socket_id: SocketId, + ) -> BackgroundTask { + // We loop reading the socket until all the pending reads are either + // being processed in a background task or they are empty + let mut ret: FuturesOrdered> = Default::default(); + loop { + break match self { + Self::TcpListener { + socket, + next_accept, + } => { + if next_accept.is_some() { + match socket.try_accept() { + Ok((mut child_socket, addr)) => { + let child_id = next_accept.take().unwrap(); + + // We set the handler on the socket so that it can + let handler = Box::new(common.handler.clone().for_socket(child_id)); + child_socket.set_handler(handler).ok(); + + // We will fix up the socket in the background then notify + // the client of the new socket + let common = common.clone(); + ret.push_back(Box::pin(async move { + // Next we record the socket so that it is active + { + let child_socket = + RemoteAdapterSocket::TcpSocket(child_socket); + let mut guard = common.sockets.lock().unwrap(); + guard.insert(child_id, child_socket); + } + + // Lastly we tell the client about the new socket + if let Some(task) = common.send(MessageResponse::FinishAccept { + socket_id, + child_id, + addr, + }) { + task.await; + } + })); + } + Err(NetworkError::WouldBlock) => {} + Err(err) => { + tracing::error!("failed to accept socket - {}", err); + } + } + } + } + Self::TcpSocket(this) => { + let mut chunk: [MaybeUninit; 10240] = + unsafe { MaybeUninit::uninit().assume_init() }; + match this.try_recv(&mut chunk) { + Ok(0) => {} + Ok(amt) => { + let chunk_unsafe: &mut [MaybeUninit] = &mut chunk[..amt]; + let chunk_unsafe: &mut [u8] = + unsafe { std::mem::transmute(chunk_unsafe) }; + if let Some(task) = common.send(MessageResponse::Recv { + socket_id, + data: chunk_unsafe.to_vec(), + }) { + ret.push_back(task); + } + continue; + } + Err(_) => {} + } + } + Self::UdpSocket(this) => { + let mut chunk: [MaybeUninit; 10240] = + unsafe { MaybeUninit::uninit().assume_init() }; + match this.try_recv_from(&mut chunk) { + Ok((0, _)) => {} + Ok((amt, addr)) => { + let chunk_unsafe: &mut [MaybeUninit] = &mut chunk[..amt]; + let chunk_unsafe: &mut [u8] = + unsafe { std::mem::transmute(chunk_unsafe) }; + if let Some(task) = common.send(MessageResponse::RecvWithAddr { + socket_id, + data: chunk_unsafe.to_vec(), + addr, + }) { + ret.push_back(task); + } + continue; + } + Err(_) => {} + } + } + Self::IcmpSocket(this) => { + let mut chunk: [MaybeUninit; 10240] = + unsafe { MaybeUninit::uninit().assume_init() }; + match this.try_recv_from(&mut chunk) { + Ok((0, _)) => {} + Ok((amt, addr)) => { + let chunk_unsafe: &mut [MaybeUninit] = &mut chunk[..amt]; + let chunk_unsafe: &mut [u8] = + unsafe { std::mem::transmute(chunk_unsafe) }; + if let Some(task) = common.send(MessageResponse::RecvWithAddr { + socket_id, + data: chunk_unsafe.to_vec(), + addr, + }) { + ret.push_back(task); + } + continue; + } + Err(_) => {} + } + } + Self::RawSocket(this) => { + let mut chunk: [MaybeUninit; 10240] = + unsafe { MaybeUninit::uninit().assume_init() }; + match this.try_recv(&mut chunk) { + Ok(0) => {} + Ok(amt) => { + let chunk_unsafe: &mut [MaybeUninit] = &mut chunk[..amt]; + let chunk_unsafe: &mut [u8] = + unsafe { std::mem::transmute(chunk_unsafe) }; + if let Some(task) = common.send(MessageResponse::Recv { + socket_id, + data: chunk_unsafe.to_vec(), + }) { + ret.push_back(task); + } + continue; + } + Err(_) => {} + } + } + }; + } + + if ret.is_empty() { + // There is nothing to process so we are done + None + } else { + Some(Box::pin(async move { + // Processes all the background tasks until completion + let mut stream = ret; + loop { + let (next, s) = stream.into_future().await; + if next.is_none() { + break; + } + stream = s; + } + })) + } + } +} + +#[derive(Debug, Default)] +struct RemoteAdapterHandlerState { + readable: HashSet, + driver_wakers: Vec, +} + +#[derive(Debug, Default, Clone)] +struct RemoteAdapterHandler { + socket_id: Option, + state: Arc>, +} +impl RemoteAdapterHandler { + pub fn for_socket(self, id: SocketId) -> Self { + Self { + socket_id: Some(id), + state: self.state, + } + } +} +impl InterestHandler for RemoteAdapterHandler { + fn interest(&mut self, interest: virtual_mio::InterestType) { + let mut guard = self.state.lock().unwrap(); + guard.driver_wakers.drain(..).for_each(|w| w.wake()); + let socket_id = match self.socket_id { + Some(s) => s, + None => return, + }; + if interest == virtual_mio::InterestType::Readable { + guard.readable.insert(socket_id); + } + } +} + +type SocketMap = HashMap; + +#[derive(Derivative)] +#[derivative(Debug)] +struct RemoteAdapterCommon { + #[derivative(Debug = "ignore")] + tx: RemoteTx, + #[derivative(Debug = "ignore")] + rx: Mutex>, + #[derivative(Debug = "ignore")] + sockets: Mutex>, + socket_accept: Mutex>, + handler: RemoteAdapterHandler, + + // The stall guard will prevent reads while its held and there are background tasks running + // (the idea behind this is to create back pressure so that the task list infinitely grow) + stall_rx: Arc>, +} +impl RemoteAdapterCommon { + fn send(self: &Arc, req: MessageResponse) -> BackgroundTask { + let this = self.clone(); + Some(Box::pin(async move { + if let Err(err) = this.tx.send(req).await { + tracing::debug!("failed to send message - {}", err); + } + })) + } +} diff --git a/lib/virtual-net/src/tests.rs b/lib/virtual-net/src/tests.rs new file mode 100644 index 00000000000..dec3f97904e --- /dev/null +++ b/lib/virtual-net/src/tests.rs @@ -0,0 +1,203 @@ +#![allow(unused)] +use std::{ + net::{Ipv4Addr, SocketAddrV4}, + sync::atomic::{AtomicU16, Ordering}, +}; + +use tracing_test::traced_test; + +#[cfg(feature = "remote")] +use crate::RemoteNetworkingServer; +use crate::{ + host::LocalNetworking, meta::FrameSerializationFormat, VirtualConnectedSocketExt, + VirtualTcpListenerExt, +}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use super::*; + +#[cfg(feature = "remote")] +async fn setup_mpsc() -> (RemoteNetworkingClient, RemoteNetworkingServer) { + tracing::info!("building MPSC channels"); + let (tx1, rx1) = tokio::sync::mpsc::channel(100); + let (tx2, rx2) = tokio::sync::mpsc::channel(100); + + tracing::info!("constructing remote client (mpsc)"); + let (client, client_driver) = RemoteNetworkingClient::new_from_mpsc(tx1, rx2); + + tracing::info!("spawning driver for remote client"); + tokio::task::spawn(client_driver); + + tracing::info!("create local networking provider"); + let local_networking = LocalNetworking::new(); + + tracing::info!("constructing remote server (mpsc)"); + let (server, server_driver) = + RemoteNetworkingServer::new_from_mpsc(tx2, rx1, Arc::new(local_networking)); + + tracing::info!("spawning driver for remote server"); + tokio::task::spawn(server_driver); + + (client, server) +} + +#[cfg(feature = "remote")] +async fn setup_pipe( + buf_size: usize, + format: FrameSerializationFormat, +) -> (RemoteNetworkingClient, RemoteNetworkingServer) { + tracing::info!("building duplex streams"); + let (tx1, rx1) = tokio::io::duplex(buf_size); + let (tx2, rx2) = tokio::io::duplex(buf_size); + + tracing::info!("constructing remote client (mpsc)"); + let (client, client_driver) = RemoteNetworkingClient::new_from_async_io(tx1, rx2, format); + + tracing::info!("spawning driver for remote client"); + tokio::task::spawn(client_driver); + + tracing::info!("create local networking provider"); + let local_networking = LocalNetworking::new(); + + tracing::info!("constructing remote server (mpsc)"); + let (server, server_driver) = + RemoteNetworkingServer::new_from_async_io(tx2, rx1, format, Arc::new(local_networking)); + + tracing::info!("spawning driver for remote server"); + tokio::task::spawn(server_driver); + + (client, server) +} + +#[cfg(feature = "remote")] +async fn test_tcp(client: RemoteNetworkingClient, _server: RemoteNetworkingServer) { + static PORT: AtomicU16 = AtomicU16::new(8000); + let addr = SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::LOCALHOST, + PORT.fetch_add(1, Ordering::SeqCst), + )); + tracing::info!("listening on {addr}"); + let mut listener = client + .listen_tcp(addr.clone(), false, false, false) + .await + .unwrap(); + + const TEST1: &'static str = "the cat ran up the wall!"; + const TEST2: &'static str = "...and fell off the roof! raise the roof! oop oop"; + + tracing::info!("spawning acceptor worker thread"); + tokio::task::spawn(async move { + tracing::info!("waiting for connection"); + let (mut socket, addr) = listener.accept().await.unwrap(); + tracing::info!("accepted connection from {addr}"); + + tracing::info!("receiving data from client"); + let mut buf = [0u8; TEST1.len()]; + socket.read_exact(&mut buf).await.unwrap(); + + let msg = String::from_utf8_lossy(&buf); + assert_eq!(msg.as_ref(), TEST1); + + tracing::info!("sending back test string - {TEST2}"); + socket.send(TEST2.as_bytes()).await.unwrap(); + }); + + tracing::info!("connecting to listening socket"); + let mut socket = client + .connect_tcp( + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)), + addr, + ) + .await + .unwrap(); + + tracing::info!("sending test string - {TEST1}"); + socket.write_all(TEST1.as_bytes()).await.unwrap(); + + tracing::info!("receiving data from server"); + let mut buf = [0u8; TEST2.len()]; + socket.read_exact(&mut buf).await.unwrap(); + + let msg = String::from_utf8_lossy(&buf); + assert_eq!(msg.as_ref(), TEST2); + + tracing::info!("all good"); +} + +#[cfg(feature = "remote")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_mpsc() { + let (client, server) = setup_mpsc().await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_small_pipe_using_bincode() { + let (client, server) = setup_pipe(10, FrameSerializationFormat::Bincode).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_large_pipe_using_bincode() { + let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Bincode).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "json")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_small_pipe_using_json() { + let (client, server) = setup_pipe(10, FrameSerializationFormat::Json).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "json")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_large_pipe_json_using_json() { + let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Json).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "messagepack")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_small_pipe_using_messagepack() { + let (client, server) = setup_pipe(10, FrameSerializationFormat::MessagePack).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "messagepack")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_large_pipe_json_using_messagepack() { + let (client, server) = setup_pipe(1024000, FrameSerializationFormat::MessagePack).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "cbor")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_small_pipe_using_cbor() { + let (client, server) = setup_pipe(10, FrameSerializationFormat::Cbor).await; + test_tcp(client, server).await +} + +#[cfg(feature = "remote")] +#[cfg(feature = "cbor")] +#[traced_test] +#[tokio::test] +async fn test_tcp_with_large_pipe_json_using_cbor() { + let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Cbor).await; + test_tcp(client, server).await +} diff --git a/lib/wasi-web/.gitignore b/lib/wasi-web/.gitignore index a0142e077e0..3ff3a0b1647 100644 --- a/lib/wasi-web/.gitignore +++ b/lib/wasi-web/.gitignore @@ -3,6 +3,7 @@ dist .parcel-cache .cache .vscode +.cargo target pkg wapm/*.wasm diff --git a/lib/wasi-web/Cargo.lock b/lib/wasi-web/Cargo.lock index 9caf184d629..71392050ba0 100644 --- a/lib/wasi-web/Cargo.lock +++ b/lib/wasi-web/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] @@ -72,7 +72,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -222,9 +222,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -301,9 +304,9 @@ dependencies = [ [[package]] name = "critical-section" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "crypto-common" @@ -359,7 +362,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -381,7 +384,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -470,6 +473,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea6672d73216c05740850c789368d371ca226dc8104d5f2e30c74252d5d6e5e" +[[package]] +name = "educe" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "079044df30bb07de7d846d41a184c4b00e66ebdac93ee459253474f3a47e50ae" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -490,6 +505,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "enumset" version = "1.1.2" @@ -508,7 +536,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -519,9 +547,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -555,13 +583,13 @@ checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -658,7 +686,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -881,9 +909,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -932,9 +960,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -1019,6 +1047,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -1138,29 +1187,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -1313,9 +1362,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -1325,9 +1374,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -1413,9 +1462,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", @@ -1468,9 +1517,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.175" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -1498,20 +1547,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -1650,9 +1699,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -1667,9 +1716,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -1678,15 +1727,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.10" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand 2.0.0", @@ -1731,7 +1780,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1789,11 +1838,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd" dependencies = [ - "autocfg", "backtrace", "bytes", "pin-project-lite", @@ -1808,7 +1856,36 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", +] + +[[package]] +name = "tokio-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" +dependencies = [ + "bincode", + "bytes", + "educe", + "futures-core", + "futures-sink", + "pin-project", + "serde", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", ] [[package]] @@ -1875,7 +1952,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2058,6 +2135,8 @@ dependencies = [ "async-trait", "bytes", "derivative", + "futures", + "serde", "thiserror", "tracing", ] @@ -2066,10 +2145,20 @@ dependencies = [ name = "virtual-net" version = "0.4.0" dependencies = [ + "anyhow", "async-trait", + "base64 0.21.2", + "bincode", "bytes", "derivative", + "futures-util", + "libc", + "pin-project-lite", + "serde", "thiserror", + "tokio", + "tokio-serde", + "tokio-util", "tracing", "virtual-mio", ] @@ -2221,7 +2310,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -2250,9 +2339,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2278,7 +2367,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2291,9 +2380,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -2305,9 +2394,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote", @@ -2315,9 +2404,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a3d1b4a575ffb873679402b2aedb3117555eb65c27b1b86c8a91e574bc2a2a" +checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" dependencies = [ "leb128", ] @@ -2526,6 +2615,7 @@ dependencies = [ "derivative", "dummy-waker", "fastrand 1.9.0", + "form_urlencoded", "futures", "http", "js-sys", @@ -2542,6 +2632,7 @@ dependencies = [ "tracing-subscriber", "tracing-wasm", "url", + "virtual-net", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -2570,9 +2661,9 @@ dependencies = [ [[package]] name = "wast" -version = "62.0.0" +version = "62.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f7ee878019d69436895f019b65f62c33da63595d8e857cbdc87c13ecb29a32" +checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f" dependencies = [ "leb128", "memchr", @@ -2582,18 +2673,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295572bf24aa5b685a971a83ad3e8b6e684aaad8a9be24bc7bf59bed84cc1c08" +checksum = "842e15861d203fb4a96d314b0751cdeaf0f6f8b35e8d81d2953af2af5e44e637" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2601,9 +2692,9 @@ dependencies = [ [[package]] name = "webc" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c47bf71ed54afd81b2f92ac7cb6f7811b4233b6088e1ee6477794aae4e05ac" +checksum = "e5c35d27cb4c7898571b5f25036ead587736ffb371261f9e928a28edee7abf9d" dependencies = [ "anyhow", "base64 0.21.2", @@ -2788,9 +2879,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] @@ -2806,9 +2897,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] diff --git a/lib/wasi-web/Cargo.toml b/lib/wasi-web/Cargo.toml index 59bff2107b1..dbb2d49a7af 100644 --- a/lib/wasi-web/Cargo.toml +++ b/lib/wasi-web/Cargo.toml @@ -18,10 +18,12 @@ rust-version = "1.67" [dependencies] wasmer = { path = "../api", default_features = false, features = [ "js-default" ] } wasmer-wasix = { path = "../wasix", version = "0.11.0", default-features = false, features = [ "js-default" ] } +virtual-net = { path = "../virtual-net", default-features = false, features = [ "remote" ] } #wasm-bindgen = { version = "0.2", features = [ "nightly", "serde-serialize" ] } wasm-bindgen = { version = "0.2", features = [ "serde-serialize" ] } wasm-bindgen-futures = "0.4" console_error_panic_hook = "^0.1" +form_urlencoded = "1.2" js-sys = "0.3" tracing = { version = "^0.1", features = [ "log", "release_max_level_info" ] } #tracing = { version = "^0.1", features = [ "log" ] } @@ -139,16 +141,16 @@ build-deps = "^0.1" [lib] crate-type = ["cdylib"] +[profile.dev] +opt-level = 1 +debug = "line-tables-only" + +[profile.release] +lto = true +opt-level = 'z' + [package.metadata.wasm-pack.profile.dev] -# Should `wasm-opt` be used to further optimize the wasm binary generated after -# the Rust compiler has finished? Using `wasm-opt` can often further decrease -# binary size or do clever tricks that haven't made their way into LLVM yet. -# -# Configuration is set to `false` by default for the dev profile, but it can -# be set to an array of strings which are explicit arguments to pass to -# `wasm-opt`. For example `['-Os']` would optimize for size while `['-O4']` -# would execute very expensive optimizations passes -wasm-opt = ['--strip-debug --enable-reference-types'] +wasm-opt = false [package.metadata.wasm-pack.profile.dev.wasm-bindgen] debug-js-glue = true @@ -156,11 +158,7 @@ demangle-name-section = true dwarf-debug-info = true [package.metadata.wasm-pack.profile.release] -# The version of wasm-opt that wasm-pack bundles crashes on current wasm-bindgen -# .wasm files. Current wasm-opt (version 93) crashes on the DWARF info that -# wasm-bindgen produces. So, we'll just disable wasm-opt for now. -wasm-opt = false #["-O4"] -#wasm-opt = ['--strip-debug --enable-reference-types'] +wasm-opt = ['-Oz'] [package.metadata.wasm-pack.profile.release.wasm-bindgen] debug-js-glue = false diff --git a/lib/wasi-web/app.yaml b/lib/wasi-web/app.yaml index 03104bbb4fe..23ec6404a2a 100644 --- a/lib/wasi-web/app.yaml +++ b/lib/wasi-web/app.yaml @@ -1,4 +1,5 @@ --- kind: wasmer.io/App.v0 name: wasmer-sh -package: wasmer/wasmer-sh +app_id: da_AKo6IotJUgY2 +package: wasmer/wasmer-sh@0.2.21 diff --git a/lib/wasi-web/cfg/config.toml b/lib/wasi-web/cfg/config.toml index f922e474006..874f73b900e 100644 --- a/lib/wasi-web/cfg/config.toml +++ b/lib/wasi-web/cfg/config.toml @@ -1,12 +1,12 @@ [general] host = "::" -port = 80 +port = 8080 root = "/public" log-level = "info" cache-control-headers = false compression = true -threads-multiplier = 16 -max-blocking-threads = 32 +threads-multiplier = 8 +max-blocking-threads = 16 [advanced] diff --git a/lib/wasi-web/cors.json b/lib/wasi-web/cors.json new file mode 100644 index 00000000000..70ae5fb74b3 --- /dev/null +++ b/lib/wasi-web/cors.json @@ -0,0 +1,8 @@ +[ + { + "origin": ["*"], + "method": ["GET", "OPTIONS"], + "responseHeader": ["*"], + "maxAgeSeconds": 3600 + } +] diff --git a/lib/wasi-web/js/index.js b/lib/wasi-web/js/index.js index abf6eafd7a4..a5d625e6d8c 100644 --- a/lib/wasi-web/js/index.js +++ b/lib/wasi-web/js/index.js @@ -2,10 +2,24 @@ import init, { start } from '../pkg/index.js'; import 'regenerator-runtime/runtime.js' import './workers-polyfill.js' +if (location.protocol !== 'https:') { + if (location.hostname !== 'localhost') { + location.replace(`https:${location.href.substring(location.protocol.length)}`); + } +} + +async function init_encoded() { + let init_cfg = await (await fetch("/init.json")).json(); + return 'init=' + encodeURIComponent(init_cfg.init) + '&' + + 'uses=' + encodeURIComponent(init_cfg.uses) + '&' + + 'prompt=' + encodeURIComponent(init_cfg.prompt) + '&' + + 'no_welcome=' + encodeURIComponent(init_cfg.no_welcome); +} + async function run() { Error.stackTraceLimit = 20; await init(); - await start(); + await start(await init_encoded()); } run(); diff --git a/lib/wasi-web/package.json b/lib/wasi-web/package.json index 0b5c38d7e22..560c5be75f2 100644 --- a/lib/wasi-web/package.json +++ b/lib/wasi-web/package.json @@ -7,7 +7,7 @@ "type": "module", "scripts": { "build": "webpack", - "dev": "webpack serve" + "dev": "webpack serve --mode development" }, "dependencies": { "comlink": "^4", @@ -27,4 +27,4 @@ "webpack-cli": "^4.8", "webpack-dev-server": "^4.9.0" } -} +} \ No newline at end of file diff --git a/lib/wasi-web/public/init.json b/lib/wasi-web/public/init.json new file mode 100644 index 00000000000..2a440f4895d --- /dev/null +++ b/lib/wasi-web/public/init.json @@ -0,0 +1,8 @@ +{ + "init": "sharrattj/bash", + "uses": [ + "john-sharratt/catsay" + ], + "prompt": "wasmer.sh", + "no_welcome": false +} diff --git a/lib/wasi-web/src/glue.rs b/lib/wasi-web/src/glue.rs index 8b38c581896..16b6e2ca7bd 100644 --- a/lib/wasi-web/src/glue.rs +++ b/lib/wasi-web/src/glue.rs @@ -7,6 +7,7 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::mpsc; #[allow(unused_imports, dead_code)] use tracing::{debug, error, info, trace, warn}; +use virtual_net::{UnsupportedVirtualNetworking, VirtualNetworking}; use wasm_bindgen::{prelude::*, JsCast}; use wasmer_wasix::{ capabilities::Capabilities, @@ -23,7 +24,10 @@ use xterm_js_rs::addons::webgl::WebglAddon; use xterm_js_rs::{LogLevel, OnKeyEvent, Terminal, TerminalOptions, Theme}; use super::{common::*, pool::*}; -use crate::runtime::{TermStdout, TerminalCommandRx, WebRuntime}; +use crate::{ + net::connect_networking, + runtime::{TermStdout, TerminalCommandRx, WebRuntime}, +}; #[macro_export] #[doc(hidden)] @@ -37,11 +41,54 @@ pub fn main() { set_panic_hook(); } -pub const DEFAULT_BOOT_WEBC: &str = "sharrattj/bash"; -pub const DEFAULT_BOOT_USES: [&str; 1] = ["sharrattj/coreutils"]; +#[derive(Debug, Clone, Default)] +pub struct StartArgs { + init: Option, + uses: Vec, + prompt: Option, + no_welcome: bool, + connect: Option, + token: Option, +} +impl StartArgs { + pub fn parse(mut self, args: &str) -> Self { + let query_pairs = || form_urlencoded::parse(args.as_bytes()); + + let find = |key| { + query_pairs() + .filter(|(k, _)| k == key) + .map(|a| a.1) + .filter(|a| a != "undefined") + .next() + }; + + if let Some(val) = find("init") { + self.init = Some(val.to_string()); + } + if let Some(val) = find("uses") { + self.uses = val.split(",").map(|v| v.to_string()).collect(); + } + if let Some(val) = find("prompt") { + self.prompt = Some(val.to_string()); + } + if let Some(val) = find("connect") { + self.connect = Some(val.to_string()); + } + if let Some(val) = find("no_welcome") { + match val.as_ref() { + "true" | "yes" | "" => self.no_welcome = true, + _ => {} + } + } + if let Some(val) = find("token") { + self.token = Some(val.to_string()); + } + self + } +} #[wasm_bindgen] -pub fn start() -> Result<(), JsValue> { +pub fn start(encoded_args: String) -> Result<(), JsValue> { #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = navigator, js_name = userAgent)] @@ -82,6 +129,13 @@ pub fn start() -> Result<(), JsValue> { let is_mobile = wasmer_wasix::os::common::is_mobile(&user_agent); debug!("user_agent: {}", user_agent); + // Compute the configuration + let mut args = StartArgs::default().parse(&encoded_args); + let location = url::Url::parse(location.as_str()).unwrap(); + if let Some(query) = location.query() { + args = args.parse(query); + } + let elem = window .document() .unwrap() @@ -129,7 +183,18 @@ pub fn start() -> Result<(), JsValue> { let stdout = TermStdout::new(term_tx, tty_options.clone()); let stderr = stdout.clone(); - let runtime = Arc::new(WebRuntime::new(pool.clone(), tty_options.clone(), webgl2)); + let mut net: Arc = + Arc::new(UnsupportedVirtualNetworking::default()); + if let Some(connect) = args.connect { + net = Arc::new(connect_networking(connect)) + } + + let runtime = Arc::new(WebRuntime::new( + pool.clone(), + tty_options.clone(), + webgl2, + net, + )); let mut tty = Tty::new( Box::new(stdin_tx), Box::new(stdout.clone()), @@ -137,52 +202,28 @@ pub fn start() -> Result<(), JsValue> { tty_options, ); - let location = url::Url::parse(location.as_str()).unwrap(); - let mut console = if let Some(init) = location - .query_pairs() - .filter(|(key, _)| key == "init") - .next() - .map(|(_, val)| val.to_string()) - { - let mut console = Console::new(init.as_str(), runtime.clone()); - console = console.with_no_welcome(true); - console - } else { - let mut console = Console::new(DEFAULT_BOOT_WEBC, runtime.clone()); - console = console.with_uses(DEFAULT_BOOT_USES.iter().map(|a| a.to_string()).collect()); - console - }; + let init = args.init.ok_or(JsValue::from_str( + "no initialization package has been specified", + ))?; + let prompt = args + .prompt + .ok_or(JsValue::from_str("no prompt has been specified"))?; - let mut env = HashMap::new(); - if let Some(origin) = location.domain().clone() { - env.insert("ORIGIN".to_string(), origin.to_string()); - } - env.insert("LOCATION".to_string(), location.to_string()); + let mut console = Console::new(init.as_str(), runtime.clone()) + .with_no_welcome(args.no_welcome) + .with_prompt(prompt); - if let Some(prompt) = location - .query_pairs() - .filter(|(key, _)| key == "prompt") - .next() - .map(|(_, val)| val.to_string()) - { - console = console.with_prompt(prompt); - } + console = console.with_uses(args.uses); - if location - .query_pairs() - .any(|(key, _)| key == "no_welcome" || key == "no-welcome") - { - console = console.with_no_welcome(true); + if let Some(token) = args.token { + console = console.with_token(token); } - if let Some(token) = location - .query_pairs() - .filter(|(key, _)| key == "token") - .next() - .map(|(_, val)| val.to_string()) - { - console = console.with_token(token); + let mut env = HashMap::new(); + if let Some(origin) = location.domain().clone() { + env.insert("ORIGIN".to_string(), origin.to_string()); } + env.insert("LOCATION".to_string(), location.to_string()); console = console .with_user_agent(user_agent.as_str()) @@ -193,7 +234,7 @@ pub fn start() -> Result<(), JsValue> { let mut capabilities = Capabilities::default(); capabilities.threading.max_threads = Some(50); - capabilities.threading.enable_asynchronous_threading = true; + capabilities.threading.enable_asynchronous_threading = false; console = console.with_capabilities(capabilities); let (tx, mut rx) = mpsc::unbounded_channel(); diff --git a/lib/wasi-web/src/lib.rs b/lib/wasi-web/src/lib.rs index 15eb0b235ed..f7407187173 100644 --- a/lib/wasi-web/src/lib.rs +++ b/lib/wasi-web/src/lib.rs @@ -2,7 +2,9 @@ mod common; mod glue; mod interval; mod module_cache; +mod net; mod pool; mod runtime; +mod ws; pub use glue::start; diff --git a/lib/wasi-web/src/net.rs b/lib/wasi-web/src/net.rs new file mode 100644 index 00000000000..32224daa9ce --- /dev/null +++ b/lib/wasi-web/src/net.rs @@ -0,0 +1,124 @@ +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +use tokio::sync::mpsc; +use virtual_net::{meta::MessageRequest, RemoteNetworkingClient}; +use wasm_bindgen_futures::JsFuture; + +use crate::{runtime::bindgen_sleep, ws::WebSocket}; + +pub fn connect_networking(connect: String) -> RemoteNetworkingClient { + let (recv_tx, recv_rx) = mpsc::channel(100); + let (send_tx, send_rx) = mpsc::channel(100); + let send_tx2 = send_tx.clone(); + + let (client, driver) = virtual_net::RemoteNetworkingClient::new_from_mpsc(send_tx, recv_rx); + wasm_bindgen_futures::spawn_local(driver); + + let send_rx = Arc::new(tokio::sync::Mutex::new(send_rx)); + + wasm_bindgen_futures::spawn_local(async move { + let backoff = Arc::new(AtomicUsize::new(0)); + loop { + // Exponential backoff prevents thrashing of the connection + let backoff_ms = backoff.load(Ordering::SeqCst); + if backoff_ms > 0 { + let promise = bindgen_sleep(backoff_ms as i32); + JsFuture::from(promise).await.ok(); + } + let new_backoff = 8000usize.min((backoff_ms * 2) + 100); + backoff.store(new_backoff, Ordering::SeqCst); + + // Establish a websocket connection to the edge network + let mut ws = match WebSocket::new(connect.as_str()) { + Ok(ws) => ws, + Err(err) => { + tracing::error!("failed to establish web socket connection - {}", err); + continue; + } + }; + + // Wire up the events + let (relay_tx, mut relay_rx) = mpsc::unbounded_channel(); + let (connected_tx, mut connected_rx) = mpsc::unbounded_channel(); + ws.set_onopen({ + let connect = connect.clone(); + let connected_tx = connected_tx.clone(); + Box::new(move || { + tracing::debug!(url = connect, "networking web-socket opened"); + connected_tx.send(true).ok(); + }) + }); + ws.set_onclose({ + let connect = connect.clone(); + + let connected_tx = connected_tx.clone(); + let relay_tx = relay_tx.clone(); + Box::new(move || { + tracing::debug!(url = connect, "networking web-socket closed"); + relay_tx.send(Vec::new()).ok(); + connected_tx.send(false).ok(); + }) + }); + ws.set_onmessage({ + Box::new(move |data| { + relay_tx.send(data).unwrap(); + }) + }); + + // Wait for it to connect and setup the rest of the callbacks + if !connected_rx.recv().await.unwrap_or_default() { + continue; + } + backoff.store(100, Ordering::SeqCst); + + // We process any backends + wasm_bindgen_futures::spawn_local({ + let send_tx2 = send_tx2.clone(); + let recv_tx = recv_tx.clone(); + async move { + while let Some(data) = relay_rx.recv().await { + if data.is_empty() { + break; + } + let data = match bincode::deserialize(&data) { + Ok(d) => d, + Err(err) => { + tracing::error!( + "failed to deserialize networking message - {}", + err + ); + break; + } + }; + if recv_tx.send(data).await.is_err() { + break; + } + } + send_tx2.try_send(MessageRequest::Reconnect).ok(); + } + }); + + while let Some(data) = send_rx.lock().await.recv().await { + if let MessageRequest::Reconnect = &data { + tracing::info!("websocket will reconnect"); + break; + } + let data = match bincode::serialize(&data) { + Ok(d) => d, + Err(err) => { + tracing::error!("failed to serialize networking message - {}", err); + break; + } + }; + if let Err(err) = ws.send(data) { + tracing::error!("websocket has failed - {}", err); + break; + } + } + } + }); + client +} diff --git a/lib/wasi-web/src/runtime.rs b/lib/wasi-web/src/runtime.rs index 586633dde7f..202068dddd0 100644 --- a/lib/wasi-web/src/runtime.rs +++ b/lib/wasi-web/src/runtime.rs @@ -26,7 +26,7 @@ use wasmer_wasix::{ resolver::{Source, WapmSource}, task_manager::TaskWasm, }, - VirtualFile, VirtualNetworking, VirtualTaskManager, WasiThreadError, WasiTtyState, + VirtualFile, VirtualTaskManager, WasiThreadError, WasiTtyState, }; use web_sys::WebGl2RenderingContext; @@ -67,6 +67,7 @@ impl WebRuntime { pool: WebThreadPool, tty_options: TtyOptions, webgl2: WebGl2RenderingContext, + net: wasmer_wasix::virtual_net::DynVirtualNetworking, ) -> WebRuntime { #[cfg(feature = "webgl")] let webgl_tx = GlContext::init(webgl2); @@ -95,7 +96,7 @@ impl WebRuntime { #[cfg(feature = "webgl")] webgl_tx, http_client, - net: Arc::new(WebVirtualNetworking), + net, module_cache: Arc::new(module_cache), package_loader: Arc::new(package_loader), source: Arc::new(source), @@ -103,11 +104,6 @@ impl WebRuntime { } } -#[derive(Clone, Debug)] -struct WebVirtualNetworking; - -impl VirtualNetworking for WebVirtualNetworking {} - #[derive(Debug, Clone)] pub(crate) struct WebTaskManager { pool: WebThreadPool, diff --git a/lib/wasi-web/src/ws.rs b/lib/wasi-web/src/ws.rs index 138aef8367d..28ed9c6bcd8 100644 --- a/lib/wasi-web/src/ws.rs +++ b/lib/wasi-web/src/ws.rs @@ -16,12 +16,12 @@ impl WebSocket { // Open the web socket let ws_sys = WebSocketSys::new(url).map_err(|err| format!("{:?}", err))?; - Ok(Box::new(Self { sys: ws_sys })) + Ok(Self { sys: ws_sys }) } } impl WebSocket { - fn set_onopen(&mut self, mut callback: Box) { + pub fn set_onopen(&mut self, mut callback: Box) { let callback = Closure::wrap(Box::new(move |_e: web_sys::ProgressEvent| { callback.deref_mut()(); }) as Box); @@ -29,7 +29,7 @@ impl WebSocket { callback.forget(); } - fn set_onclose(&mut self, callback: Box) { + pub fn set_onclose(&mut self, callback: Box) { let callback = Closure::wrap(Box::new(move |_e: web_sys::ProgressEvent| { callback.deref()(); }) as Box); @@ -38,10 +38,7 @@ impl WebSocket { callback.forget(); } - fn set_onmessage( - &mut self, - callback: Box) + Send + 'static>, - ) { + pub fn set_onmessage(&mut self, callback: Box) + Send + 'static>) { let callback = Arc::new(callback); let fr = web_sys::FileReader::new().unwrap(); @@ -79,7 +76,7 @@ impl WebSocket { onmessage_callback.forget(); } - fn send(&mut self, data: Vec) -> Result<(), String> { + pub fn send(&mut self, data: Vec) -> Result<(), String> { let data_len = data.len(); let array = js_sys::Uint8Array::new_with_length(data_len as u32); array.copy_from(&data[..]); diff --git a/lib/wasi-web/wasmer.toml b/lib/wasi-web/wasmer.toml index e684ff8a0f6..a58e58b7e56 100644 --- a/lib/wasi-web/wasmer.toml +++ b/lib/wasi-web/wasmer.toml @@ -1,11 +1,11 @@ [package] -name = 'wasmer/wasmer-sh-async' -version = '1.0.3' -description = 'Container that holds the wasmer.sh website.' +name = "wasmer/wasmer-sh" +version = "0.2.26" +description = "Container that holds the wasmer.sh website." [dependencies] -"wasmer/static-web-server-async" = '1' +"wasmer/static-web-server" = "1" [fs] -public = 'dist' -cfg = 'cfg' +public = "dist" +cfg = "cfg" diff --git a/lib/wasi-web/webpack.config.mjs b/lib/wasi-web/webpack.config.mjs index 5a661059240..e426e9cc7c3 100644 --- a/lib/wasi-web/webpack.config.mjs +++ b/lib/wasi-web/webpack.config.mjs @@ -17,6 +17,7 @@ export default { { from: resolve(__dirname, "public/wasmer.css") }, { from: resolve(__dirname, "public/worker.js") }, { from: resolve(__dirname, "public/favicon.png") }, + { from: resolve(__dirname, "public/init.json") }, ], }), new WasmPackPlugin({ diff --git a/lib/wasix/Cargo.toml b/lib/wasix/Cargo.toml index 8719d0e71d0..44de8219ca4 100644 --- a/lib/wasix/Cargo.toml +++ b/lib/wasix/Cargo.toml @@ -127,6 +127,7 @@ host-vnet = ["virtual-net/host-net"] host-threads = [] host-reqwest = ["reqwest"] host-fs = ["virtual-fs/host-fs"] +remote-vnet = ["virtual-net/remote"] logging = ["tracing/log"] disable-all-logging = ["tracing/release_max_level_off", "tracing/max_level_off"] diff --git a/lib/wasix/src/fs/inode_guard.rs b/lib/wasix/src/fs/inode_guard.rs index 1fb7c424e8d..c6d571c9613 100644 --- a/lib/wasix/src/fs/inode_guard.rs +++ b/lib/wasix/src/fs/inode_guard.rs @@ -12,7 +12,7 @@ use futures::future::BoxFuture; use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite}; use virtual_fs::{FsError, Pipe as VirtualPipe, VirtualFile}; use virtual_mio::{InterestType, StatefulHandler}; -use virtual_net::NetworkError; +use virtual_net::net_error_into_io_err; use wasmer_wasix_types::{ types::Eventtype, wasi::{self, EpollType}, @@ -748,42 +748,3 @@ fn is_err_closed(err: &std::io::Error) -> bool { || err.kind() == std::io::ErrorKind::NotConnected || err.kind() == std::io::ErrorKind::UnexpectedEof } - -pub fn net_error_into_io_err(net_error: NetworkError) -> std::io::Error { - use std::io::ErrorKind; - match net_error { - NetworkError::InvalidFd => ErrorKind::BrokenPipe.into(), - NetworkError::AlreadyExists => ErrorKind::AlreadyExists.into(), - NetworkError::Lock => ErrorKind::BrokenPipe.into(), - NetworkError::IOError => ErrorKind::BrokenPipe.into(), - NetworkError::AddressInUse => ErrorKind::AddrInUse.into(), - NetworkError::AddressNotAvailable => ErrorKind::AddrNotAvailable.into(), - NetworkError::BrokenPipe => ErrorKind::BrokenPipe.into(), - NetworkError::ConnectionAborted => ErrorKind::ConnectionAborted.into(), - NetworkError::ConnectionRefused => ErrorKind::ConnectionRefused.into(), - NetworkError::ConnectionReset => ErrorKind::ConnectionReset.into(), - NetworkError::Interrupted => ErrorKind::Interrupted.into(), - NetworkError::InvalidData => ErrorKind::InvalidData.into(), - NetworkError::InvalidInput => ErrorKind::InvalidInput.into(), - NetworkError::NotConnected => ErrorKind::NotConnected.into(), - NetworkError::NoDevice => ErrorKind::BrokenPipe.into(), - NetworkError::PermissionDenied => ErrorKind::PermissionDenied.into(), - NetworkError::TimedOut => ErrorKind::TimedOut.into(), - NetworkError::UnexpectedEof => ErrorKind::UnexpectedEof.into(), - NetworkError::WouldBlock => ErrorKind::WouldBlock.into(), - NetworkError::WriteZero => ErrorKind::WriteZero.into(), - NetworkError::Unsupported => ErrorKind::Unsupported.into(), - NetworkError::UnknownError => ErrorKind::BrokenPipe.into(), - NetworkError::InsufficientMemory => ErrorKind::OutOfMemory.into(), - NetworkError::TooManyOpenFiles => { - #[cfg(target_family = "unix")] - { - std::io::Error::from_raw_os_error(libc::EMFILE) - } - #[cfg(not(target_family = "unix"))] - { - ErrorKind::Other.into() - } - } - } -} diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index 091d560b4b2..f57f753c95f 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -32,9 +32,8 @@ use wasmer_wasix_types::{ pub use self::fd::{EpollFd, EpollInterest, EpollJoinGuard, Fd, InodeVal, Kind}; pub(crate) use self::inode_guard::{ - net_error_into_io_err, InodeValFilePollGuard, InodeValFilePollGuardJoin, - InodeValFilePollGuardMode, InodeValFileReadGuard, InodeValFileWriteGuard, WasiStateFileGuard, - POLL_GUARD_MAX_RET, + InodeValFilePollGuard, InodeValFilePollGuardJoin, InodeValFilePollGuardMode, + InodeValFileReadGuard, InodeValFileWriteGuard, WasiStateFileGuard, POLL_GUARD_MAX_RET, }; pub use self::notification::NotificationInner; use crate::syscalls::map_io_err; diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index 3ea2b4cd836..f3db61da7b3 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -74,8 +74,9 @@ pub use virtual_net; pub use virtual_net::{UnsupportedVirtualNetworking, VirtualNetworking}; #[cfg(feature = "host-vnet")] -pub use virtual_net::host::{ - io_err_into_net_error, LocalNetworking, LocalTcpListener, LocalTcpStream, LocalUdpSocket, +pub use virtual_net::{ + host::{LocalNetworking, LocalTcpListener, LocalTcpStream, LocalUdpSocket}, + io_err_into_net_error, }; use wasmer_wasix_types::wasi::{Errno, ExitCode}; diff --git a/lib/wasix/src/os/console/mod.rs b/lib/wasix/src/os/console/mod.rs index 66e19cfa88a..886b44ab2e3 100644 --- a/lib/wasix/src/os/console/mod.rs +++ b/lib/wasix/src/os/console/mod.rs @@ -4,10 +4,11 @@ pub mod cconst; use std::{ + borrow::Cow, collections::HashMap, io::Write, ops::{Deref, DerefMut}, - path::Path, + path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc, Mutex}, }; @@ -18,7 +19,7 @@ use tokio::sync::{mpsc, RwLock}; use tracing::{debug, error, info, trace, warn}; use virtual_fs::{ ArcBoxFile, ArcFile, AsyncWriteExt, CombineFile, DeviceFile, DuplexPipe, FileSystem, Pipe, - PipeRx, PipeTx, RootFileSystemBuilder, VirtualFile, + PipeRx, PipeTx, RootFileSystemBuilder, StaticFile, VirtualFile, }; #[cfg(feature = "sys")] use wasmer::Engine; @@ -51,6 +52,7 @@ pub struct Console { stdout: ArcBoxFile, stderr: ArcBoxFile, capabilities: Capabilities, + ro_files: HashMap>, memfs_memory_limiter: Option, } @@ -73,6 +75,7 @@ impl Console { stderr: ArcBoxFile::new(Box::new(Pipe::channel().0)), capabilities: Default::default(), memfs_memory_limiter: None, + ro_files: Default::default(), } } @@ -135,6 +138,11 @@ impl Console { self } + pub fn with_ro_files(mut self, ro_files: HashMap>) -> Self { + self.ro_files = ro_files; + self + } + pub fn with_mem_fs_memory_limiter( mut self, limiter: virtual_fs::limiter::DynFsMemoryLimiter, @@ -245,6 +253,23 @@ impl Console { return Err(SpawnError::BadRequest); } + // The custom readonly files have to be added after the uses packages + // otherwise they will be overriden by their attached file systems + for (path, data) in self.ro_files.clone() { + let path = PathBuf::from(path); + env.fs_root().remove_file(&path).ok(); + let mut file = env + .fs_root() + .new_open_options() + .create(true) + .truncate(true) + .write(true) + .open(&path) + .map_err(|err| SpawnError::Other(err.into()))?; + InlineWaker::block_on(file.copy_reference(Box::new(StaticFile::new(data)))) + .map_err(|err| SpawnError::Other(err.into()))?; + } + // Build the config // Run the binary let store = self.runtime.new_store(); diff --git a/lib/wasix/src/runtime/task_manager/mod.rs b/lib/wasix/src/runtime/task_manager/mod.rs index 02977367385..b866c4af659 100644 --- a/lib/wasix/src/runtime/task_manager/mod.rs +++ b/lib/wasix/src/runtime/task_manager/mod.rs @@ -1,7 +1,6 @@ // TODO: should be behind a different , tokio specific feature flag. #[cfg(feature = "sys-thread")] pub mod tokio; -mod waker; use std::ops::Deref; use std::task::{Context, Poll}; @@ -17,7 +16,7 @@ use crate::os::task::thread::WasiThreadError; use crate::syscalls::AsyncifyFuture; use crate::{capture_snapshot, InstanceSnapshot, WasiEnv, WasiFunctionEnv, WasiThread}; -pub use waker::*; +pub use virtual_mio::waker::*; #[derive(Debug)] pub enum SpawnMemoryType<'a> { diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index b8a895302fb..d0dbd1652c6 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -947,11 +947,20 @@ impl WasiEnv { let rt = self.runtime(); for package_name in uses { - let specifier = package_name - .parse::() - .map_err(|e| WasiStateCreationError::WasiIncludePackageError(e.to_string()))?; - let pkg = InlineWaker::block_on(BinaryPackage::from_registry(&specifier, rt)) - .map_err(|e| WasiStateCreationError::WasiIncludePackageError(e.to_string()))?; + let specifier = package_name.parse::().map_err(|e| { + WasiStateCreationError::WasiIncludePackageError(format!( + "package_name={package_name}, {}", + e + )) + })?; + let pkg = InlineWaker::block_on(BinaryPackage::from_registry(&specifier, rt)).map_err( + |e| { + WasiStateCreationError::WasiIncludePackageError(format!( + "package_name={package_name}, {}", + e + )) + }, + )?; self.use_package(&pkg)?; } diff --git a/lib/wasix/src/syscalls/wasix/epoll_ctl.rs b/lib/wasix/src/syscalls/wasix/epoll_ctl.rs index 47cfe78fef1..bbec212fbd4 100644 --- a/lib/wasix/src/syscalls/wasix/epoll_ctl.rs +++ b/lib/wasix/src/syscalls/wasix/epoll_ctl.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use tokio::sync::{mpsc::UnboundedSender, watch}; use virtual_mio::{InterestHandler, InterestType}; +use virtual_net::net_error_into_io_err; use wasmer_wasix_types::wasi::{ EpollCtl, EpollEvent, EpollType, SubscriptionClock, SubscriptionUnion, Userdata, }; @@ -12,8 +13,8 @@ use futures::Future; use super::*; use crate::{ fs::{ - net_error_into_io_err, EpollFd, EpollInterest, EpollJoinGuard, InodeValFilePollGuard, - InodeValFilePollGuardJoin, InodeValFilePollGuardMode, POLL_GUARD_MAX_RET, + EpollFd, EpollInterest, EpollJoinGuard, InodeValFilePollGuard, InodeValFilePollGuardJoin, + InodeValFilePollGuardMode, POLL_GUARD_MAX_RET, }, state::PollEventSet, syscalls::*, diff --git a/lib/wasix/src/syscalls/wasix/port_addr_add.rs b/lib/wasix/src/syscalls/wasix/port_addr_add.rs index 1bfa433f88d..ce4633957d5 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_add.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_add.rs @@ -21,6 +21,7 @@ pub fn port_addr_add( let net = env.net().clone(); wasi_try_ok!(__asyncify(&mut ctx, None, async { net.ip_add(cidr.ip, cidr.prefix) + .await .map_err(net_error_into_wasi_err) })?); Ok(Errno::Success) diff --git a/lib/wasix/src/syscalls/wasix/port_addr_clear.rs b/lib/wasix/src/syscalls/wasix/port_addr_clear.rs index 3129d62c09d..73afbe7577e 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_clear.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_clear.rs @@ -8,7 +8,7 @@ pub fn port_addr_clear(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result( let net = env.net().clone(); let addrs = wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.ip_list().map_err(net_error_into_wasi_err) + net.ip_list().await.map_err(net_error_into_wasi_err) })?); let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; diff --git a/lib/wasix/src/syscalls/wasix/port_addr_remove.rs b/lib/wasix/src/syscalls/wasix/port_addr_remove.rs index daab635c614..ef48d647953 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_remove.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_remove.rs @@ -20,7 +20,7 @@ pub fn port_addr_remove( let net = env.net().clone(); wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.ip_remove(ip).map_err(net_error_into_wasi_err) + net.ip_remove(ip).await.map_err(net_error_into_wasi_err) })?); Ok(Errno::Success) } diff --git a/lib/wasix/src/syscalls/wasix/port_gateway_set.rs b/lib/wasix/src/syscalls/wasix/port_gateway_set.rs index ecb9a8c8a28..5a4cfdc4198 100644 --- a/lib/wasix/src/syscalls/wasix/port_gateway_set.rs +++ b/lib/wasix/src/syscalls/wasix/port_gateway_set.rs @@ -20,7 +20,7 @@ pub fn port_gateway_set( let net = env.net().clone(); wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.gateway_set(ip).map_err(net_error_into_wasi_err) + net.gateway_set(ip).await.map_err(net_error_into_wasi_err) })?); Ok(Errno::Success) } diff --git a/lib/wasix/src/syscalls/wasix/port_mac.rs b/lib/wasix/src/syscalls/wasix/port_mac.rs index 2bd39294e1b..15de5612b19 100644 --- a/lib/wasix/src/syscalls/wasix/port_mac.rs +++ b/lib/wasix/src/syscalls/wasix/port_mac.rs @@ -13,7 +13,7 @@ pub fn port_mac( let net = env.net().clone(); let mac = wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.mac().map_err(net_error_into_wasi_err) + net.mac().await.map_err(net_error_into_wasi_err) })?); let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; diff --git a/lib/wasix/src/syscalls/wasix/port_route_add.rs b/lib/wasix/src/syscalls/wasix/port_route_add.rs index db85e487298..9e3ee6e4c88 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_add.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_add.rs @@ -36,6 +36,7 @@ pub fn port_route_add( let net = env.net().clone(); wasi_try_ok!(__asyncify(&mut ctx, None, async { net.route_add(cidr, via_router, preferred_until, expires_at) + .await .map_err(net_error_into_wasi_err) })?); Ok(Errno::Success) diff --git a/lib/wasix/src/syscalls/wasix/port_route_clear.rs b/lib/wasix/src/syscalls/wasix/port_route_clear.rs index f006b07b99a..cd853f929de 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_clear.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_clear.rs @@ -8,7 +8,7 @@ pub fn port_route_clear(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result( let net = env.net().clone(); let routes = wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.route_list().map_err(net_error_into_wasi_err) + net.route_list().await.map_err(net_error_into_wasi_err) })?); Span::current().record("nroutes", routes.len()); diff --git a/lib/wasix/src/syscalls/wasix/port_route_remove.rs b/lib/wasix/src/syscalls/wasix/port_route_remove.rs index 52068c032e6..3ab127e9e4d 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_remove.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_remove.rs @@ -16,7 +16,7 @@ pub fn port_route_remove( let net = env.net().clone(); wasi_try_ok!(__asyncify(&mut ctx, None, async { - net.route_remove(ip).map_err(net_error_into_wasi_err) + net.route_remove(ip).await.map_err(net_error_into_wasi_err) })?); Ok(Errno::Success)