diff --git a/netstack/.gitignore b/netstack/.gitignore new file mode 100644 index 0000000000..b83d22266a --- /dev/null +++ b/netstack/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/netstack/Cargo.lock b/netstack/Cargo.lock new file mode 100644 index 0000000000..e12d606f91 --- /dev/null +++ b/netstack/Cargo.lock @@ -0,0 +1,765 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "arg_parser" +version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/arg-parser.git#1c434b55f3e1a0375ebcca85b3e88db7378e82fa" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "defmt" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "dns-parser" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7020f6760aea312d43d23cb83bf6c0c49f162541db8b02bf95209ac51dc253d" +dependencies = [ + "byteorder 1.5.0", + "quick-error", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder 1.5.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ioslice" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e571352c8a3b89074d12e3ee5173ffe162159105352aaaf1fc5764da747e31b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "ioslice", + "libc", + "redox_syscall", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "net2" +version = "0.2.37" +source = "git+https://gitlab.redox-os.org/redox-os/net2-rs.git?branch=master#db0604dcb0a355e6b2fa5bcaad8f175bd6aeb5aa" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi", +] + +[[package]] +name = "netutils" +version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/netutils.git#197e0ed0a0ce637653b33f6076c08afeac8cda16" +dependencies = [ + "anyhow", + "arg_parser", + "libc", + "libredox", + "net2", + "ntpclient", + "pbr", + "redox-daemon", + "redox_event", + "redox_termios", + "termion", + "url", +] + +[[package]] +name = "ntpclient" +version = "0.0.1" +source = "git+https://github.com/willem66745/ntpclient-rust#7e3bdf60eb940825789a8da5181025320e3050b0" +dependencies = [ + "byteorder 0.5.3", + "time", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pbr" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514" +dependencies = [ + "crossbeam-channel", + "libc", + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox-daemon" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdeffdb03cf2961b211a2023e683d71f2c225ea93404da5dc34b0dc94f0341" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "redox-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81460b1526438123d16f0c968dbe42ba7f61e99645109b70e57864a8b66710fb" +dependencies = [ + "chrono", + "log", + "smallvec", + "termion", +] + +[[package]] +name = "redox_event" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69609faa5d5992247a4ef379917bb3e39be281405d6a0ccd4f942429400b956f" +dependencies = [ + "bitflags 2.6.0", + "libredox", +] + +[[package]] +name = "redox_netstack" +version = "0.1.0" +dependencies = [ + "anyhow", + "dns-parser", + "ioslice", + "libredox", + "log", + "netutils", + "redox-daemon", + "redox-log", + "redox_event", + "redox_syscall", + "smoltcp", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smoltcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" +dependencies = [ + "bitflags 1.3.2", + "byteorder 1.5.0", + "cfg-if 1.0.0", + "defmt", + "heapless", + "log", + "managed", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termion" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eaa98560e51a2cf4f0bb884d8b2098a9ea11ecf3b7078e9c68242c74cc923a7" +dependencies = [ + "libc", + "libredox", + "numtoa", + "redox_termios", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/netstack/Cargo.toml b/netstack/Cargo.toml new file mode 100644 index 0000000000..a1efb585eb --- /dev/null +++ b/netstack/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "redox_netstack" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "dnsd" +path = "src/dnsd/main.rs" + +[[bin]] +name = "smolnetd" +path = "src/smolnetd/main.rs" + +[lib] +name = "redox_netstack" +path = "src/lib/lib.rs" + +[dependencies] +netutils = { git = "https://gitlab.redox-os.org/redox-os/netutils.git" } +redox_event = "0.4.1" +redox-daemon = "0.1.2" +redox_syscall = "0.5" +redox-log = "0.1" +dns-parser = "0.7.1" +libredox = { version = "0.1.3", features = ["mkns"] } +anyhow = "1.0.81" +ioslice = "0.6.0" + +[dependencies.log] +version = "0.4" +default-features = false +features = ["release_max_level_warn"] + +[dependencies.smoltcp] +version = "0.12.0" +default-features = false +features = [ + "std", + "medium-ethernet", "medium-ip", + "proto-ipv4", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-tcp-cubic", + "iface-max-addr-count-8", + "log" +] +#For debugging: "log", "verbose" + +[profile.release] +lto = true + +[patch.crates-io] +net2 = { git = "https://gitlab.redox-os.org/redox-os/net2-rs.git", branch = "master" } diff --git a/netstack/LICENSE b/netstack/LICENSE new file mode 100644 index 0000000000..401759bf97 --- /dev/null +++ b/netstack/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2018 Redox OS Developers + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/netstack/README.md b/netstack/README.md new file mode 100644 index 0000000000..633dd04a1e --- /dev/null +++ b/netstack/README.md @@ -0,0 +1,19 @@ +# Redox OS userspace networking stack + +This repository contains the networking stack for Redox OS. It makes use of [smoltcp](https://github.com/m-labs/smoltcp). + +## How To Contribute + +To learn how to contribute to this system component you need to read the following document: + +- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) + +## Development + +To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. + +### How To Build + +To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page. + +This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux. diff --git a/netstack/src/dnsd/main.rs b/netstack/src/dnsd/main.rs new file mode 100644 index 0000000000..d47c240084 --- /dev/null +++ b/netstack/src/dnsd/main.rs @@ -0,0 +1,102 @@ +#[macro_use] +extern crate log; + +use anyhow::{Context, Result}; +use event::EventQueue; +use ioslice::IoSlice; +use libredox::Fd; +use redox_netstack::logger; +use scheme::Dnsd; +use std::fs::File; +use std::os::unix::io::{FromRawFd, RawFd}; +use std::process; + +mod scheme; + +fn run(daemon: redox_daemon::Daemon) -> Result<()> { + use libredox::flag::*; + + let dns_fd = + Fd::open(":dns", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :dns")?; + + let time_path = format!("/scheme/time/{}", CLOCK_MONOTONIC); + let time_fd = Fd::open(&time_path, O_RDWR, 0).context("failed to open time")?; + + let nameserver_fd = Fd::open( + "/scheme/netcfg/resolv/nameserver", + O_RDWR | O_CREAT | O_NONBLOCK, + 0, + ) + .context("failed to open nameserver")?; + + let event_queue = EventQueue::::new().context("failed to create event queue")?; + + event_queue + .subscribe( + dns_fd.raw(), + EventSource::DnsScheme, + event::EventFlags::READ, + ) + .context("failed to listen to time events")?; + event_queue + .subscribe( + nameserver_fd.raw(), + EventSource::NameserverScheme, + event::EventFlags::READ, + ) + .context("failed to listen to nameserver socket events")?; + event_queue + .subscribe(time_fd.raw(), EventSource::Timer, event::EventFlags::READ) + .context("failed to listen to timer events")?; + + let (dns_file, time_file) = unsafe { + ( + File::from_raw_fd(dns_fd.into_raw() as RawFd), + File::from_raw_fd(time_fd.into_raw() as RawFd), + ) + }; + + let mut dnsd = Dnsd::new(dns_file, time_file, &event_queue); + + let new_ns = + libredox::call::mkns(&[IoSlice::new(b"dns")]).expect("dnsd: failed to create namespace"); + libredox::call::setrens(new_ns, new_ns).expect("dnsd: failed to enter namespace"); + + daemon.ready().expect("dnsd: failed to notify parent"); + + for event_res in event_queue.iter() { + let event = event_res.context("failed to read from event queue")?; + match event.user_data { + EventSource::DnsScheme => { + if !dnsd.on_dns_file_event()? { + break; + } + } + EventSource::NameserverScheme => dnsd.on_nameserver_event()?, + EventSource::Timer => dnsd.on_time_event()?, + EventSource::Other => dnsd.on_unknown_fd_event(event.fd as RawFd)?, + } + } + Ok(()) +} + +fn main() { + redox_daemon::Daemon::new(move |daemon| { + logger::init_logger("dnsd"); + if let Err(err) = run(daemon) { + error!("dnsd: {}", err); + process::exit(1); + } + process::exit(0); + }) + .expect("dnsd: failed to daemonize"); +} + +event::user_data! { + enum EventSource { + DnsScheme, + NameserverScheme, + Timer, + Other, + } +} diff --git a/netstack/src/dnsd/scheme.rs b/netstack/src/dnsd/scheme.rs new file mode 100644 index 0000000000..dc766d366b --- /dev/null +++ b/netstack/src/dnsd/scheme.rs @@ -0,0 +1,548 @@ +use std::borrow::ToOwned; +use std::collections::btree_map::Entry; +use std::collections::VecDeque; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::mem; +use std::net::Ipv4Addr; +use std::os::unix::io::RawFd; +use std::rc::Rc; +use std::str; +use std::str::FromStr; + +use libredox::flag; +use syscall; +use syscall::data::TimeSpec; +use syscall::{ + Error as SyscallError, EventFlags as SyscallEventFlags, Packet as SyscallPacket, + Result as SyscallResult, SchemeMut, +}; + +use event::EventQueue; +use redox_netstack::error::{Error, Result}; + +use dns_parser::{Builder, Packet as DNSPacket, RRData, ResponseCode}; +use dns_parser::{QueryClass, QueryType}; + +use crate::EventSource; + +enum DnsFile { + Resolved { data: Rc<[u8]>, pos: usize }, + Waiting { domain: String }, + Timeout, + Failed, +} + +enum Domain { + Resolved { + data: Rc<[u8]>, + }, + Requested { + waiting_fds: BTreeSet, + socket_fd: RawFd, + }, +} + +enum DnsParsingResult { + WakeUpFiles(BTreeSet), + FailFiles(BTreeSet), +} + +struct Domains { + nameserver: Ipv4Addr, + domains: BTreeMap, Domain>, + requests: BTreeMap>, + resolved_timeouts: VecDeque<(TimeSpec, Rc)>, + requested_timeouts: VecDeque<(TimeSpec, Rc)>, +} + +impl Domains { + fn new() -> Domains { + let mut domains = Domains { + nameserver: Ipv4Addr::new(8, 8, 8, 8), + domains: BTreeMap::new(), + requests: BTreeMap::new(), + resolved_timeouts: VecDeque::new(), + requested_timeouts: VecDeque::new(), + }; + domains.update_nameserver(); + domains + } + + pub fn update_nameserver(&mut self) { + if let Ok(mut file) = File::open("/scheme/netcfg/resolv/nameserver") { + let mut nameserver = String::new(); + if file.read_to_string(&mut nameserver).is_ok() { + if let Some(line) = nameserver.lines().next() { + if let Ok(ip) = Ipv4Addr::from_str(line) { + trace!("Changing nameserver to {}", ip); + self.nameserver = ip; + } + } + } + } + } + + fn request_domain(&mut self, domain: &str, queue: &EventQueue) -> Option { + trace!("Requesting domain {}", domain); + let mut builder = Builder::new_query(1, true); + builder.add_question(domain, QueryType::A, QueryClass::IN); + let packet = builder.build().ok()?; + let udp_fd = libredox::call::open( + &format!("udp:{}:53", self.nameserver), + libredox::flag::O_RDWR | libredox::flag::O_CREAT | libredox::flag::O_NONBLOCK, + 0, + ) + .ok()?; + if libredox::call::write(udp_fd, &packet) != Ok(packet.len()) { + libredox::call::close(udp_fd).ok()?; + return None; + } + queue + .subscribe(udp_fd, EventSource::Other, event::EventFlags::READ) + .ok()?; + self.requests + .insert(udp_fd as RawFd, domain.to_owned().into()); + Some(udp_fd as RawFd) + } + + fn on_time_event( + &mut self, + cur_time: &TimeSpec, + queue: &EventQueue, + ) -> Result> { + while let Some((timeout, domain)) = self.resolved_timeouts.pop_front() { + if timeout.tv_sec > cur_time.tv_sec + || (timeout.tv_sec == cur_time.tv_sec && timeout.tv_nsec > cur_time.tv_nsec) + { + self.resolved_timeouts.push_front((timeout, domain)); + break; + } + trace!("Timing out resolved domain {:?}", domain); + match self.domains.entry(domain) { + Entry::Vacant(_) => {} + Entry::Occupied(e) => { + if let Domain::Resolved { .. } = *e.get() { + e.remove(); + } + } + } + } + + let mut fds_to_wakeup = BTreeSet::new(); + + while let Some((timeout, domain)) = self.requested_timeouts.pop_front() { + if timeout.tv_sec > cur_time.tv_sec + || (timeout.tv_sec == cur_time.tv_sec && timeout.tv_nsec > cur_time.tv_nsec) + { + self.requested_timeouts.push_front((timeout, domain)); + break; + } + trace!("Timing out requested domain {:?}", domain); + match self.domains.entry(domain) { + Entry::Vacant(_) => {} + Entry::Occupied(e) => { + if let Domain::Requested { .. } = *e.get() { + if let Domain::Requested { + mut waiting_fds, + socket_fd, + } = e.remove() + { + fds_to_wakeup.append(&mut waiting_fds); + queue.unsubscribe(socket_fd as usize).map_err(|e| { + Error::from_syscall_error(e.into(), "unsubscribe failure") + })?; + let _ = libredox::call::close(socket_fd as usize); + } + } + } + } + } + + Ok(fds_to_wakeup) + } + + fn on_fd_event( + &mut self, + fd: RawFd, + cur_time: &TimeSpec, + queue: &EventQueue, + ) -> Option { + let e = match self.requests.entry(fd) { + Entry::Vacant(_) => { + return None; + } + Entry::Occupied(e) => e, + }; + let mut buf = [0u8; 0x1000]; + let readed = libredox::call::read(fd as usize, &mut buf).ok()?; + if readed == 0 { + return None; + } + let pkt = DNSPacket::parse(&buf).ok()?; + if pkt.header.response_code != ResponseCode::NoError || pkt.answers.is_empty() { + if let Some(query) = pkt.questions.iter().next() { + if query.qname.to_string().to_lowercase() == e.get().as_ref() { + queue.unsubscribe(fd as usize).ok()?; + libredox::call::close(fd as usize).ok()?; + let domain = e.remove(); + self.requested_timeouts + .retain(|&(_, ref d)| d.as_ref() != domain.as_ref()); + if let Entry::Occupied(e) = self.domains.entry(domain) { + let domain_data = e.remove(); + return if let Domain::Requested { waiting_fds, .. } = domain_data { + Some(DnsParsingResult::FailFiles(waiting_fds)) + } else { + None + }; + } + } + } + return None; + } + let mut result = String::new(); + for answer in pkt.answers { + if answer.name.to_string().to_lowercase() != e.get().as_ref() { + continue; + } + if let RRData::A(ip) = answer.data { + result += &format!("{}\n", ip); + } + } + if result.is_empty() { + return None; + } + let data = Rc::from(result.into_bytes()); + queue.unsubscribe(fd as usize).ok()?; + libredox::call::close(fd as usize).ok()?; + let domain = e.remove(); + let mut domain_data = Domain::Resolved { data }; + trace!("On FD event {} {} resolved", fd, domain); + + let mut resolved_timeout = *cur_time; + resolved_timeout.tv_sec += Dnsd::RESOLVED_TIMEOUT_S; + + self.resolved_timeouts + .push_back((resolved_timeout, Rc::clone(&domain))); + + self.requested_timeouts + .retain(|&(_, ref d)| d.as_ref() != domain.as_ref()); + + match self.domains.entry(domain) { + Entry::Vacant(e) => { + e.insert(domain_data); + None + } + Entry::Occupied(mut e) => { + mem::swap(e.get_mut(), &mut domain_data); + if let Domain::Requested { waiting_fds, .. } = domain_data { + Some(DnsParsingResult::WakeUpFiles(waiting_fds)) + } else { + None + } + } + } + } + + fn file_from_domain( + &mut self, + domain: &str, + fd: usize, + cur_time: &TimeSpec, + queue: &EventQueue, + ) -> DnsFile { + if let Some(domain_data) = self.domains.get_mut(domain) { + match *domain_data { + Domain::Resolved { ref data } => DnsFile::Resolved { + data: Rc::clone(data), + pos: 0, + }, + Domain::Requested { + ref mut waiting_fds, + .. + } => { + waiting_fds.insert(fd); + DnsFile::Waiting { + domain: domain.to_owned(), + } + } + } + } else { + if let Some(socket_fd) = self.request_domain(domain, queue) { + let mut waiting_fds = BTreeSet::new(); + let domain = domain.to_owned().into(); + waiting_fds.insert(fd); + self.domains.insert( + Rc::clone(&domain), + Domain::Requested { + waiting_fds, + socket_fd, + }, + ); + let mut timeout = *cur_time; + timeout.tv_sec += Dnsd::REQUEST_TIMEOUT_S; + self.requested_timeouts.push_back((timeout, domain)); + } + DnsFile::Waiting { + domain: domain.to_owned(), + } + } + } + + fn unwait_fd(&mut self, domain: &str, fd: usize) { + if let Some(domain_data) = self.domains.get_mut(domain) { + if let Domain::Requested { + ref mut waiting_fds, + .. + } = *domain_data + { + waiting_fds.remove(&fd); + } + } + } +} + +pub struct Dnsd<'q> { + dns_file: File, + time_file: File, + queue: &'q EventQueue, + files: BTreeMap, + domains: Domains, + wait_map: BTreeMap, + next_fd: usize, +} + +impl<'q> Dnsd<'q> { + const RESOLVED_TIMEOUT_S: i64 = 5 * 60; + const REQUEST_TIMEOUT_S: i64 = 30; + const TIME_EVENT_TIMEOUT_S: i64 = 5; + + pub fn new(dns_file: File, time_file: File, queue: &'q EventQueue) -> Self { + Dnsd { + dns_file, + time_file, + queue, + files: BTreeMap::new(), + domains: Domains::new(), + wait_map: BTreeMap::new(), + next_fd: 1, + } + } + + pub fn on_time_event(&mut self) -> Result<()> { + let mut time = TimeSpec::default(); + if self.time_file.read(&mut time)? < mem::size_of::() { + return Err(Error::from_syscall_error( + syscall::Error::new(syscall::EBADF), + "Can't read current time", + )); + } + + let fds_to_wakeup = self.domains.on_time_event(&time, self.queue)?; + if !fds_to_wakeup.is_empty() { + for fd in &fds_to_wakeup { + if let Some(file) = self.files.get_mut(fd) { + *file = DnsFile::Timeout; + } + } + self.wakeup_fds(&fds_to_wakeup); + } + + time.tv_sec += Dnsd::TIME_EVENT_TIMEOUT_S; + self.time_file + .write_all(&time) + .map_err(|e| Error::from_io_error(e, "Failed to write to time file"))?; + Ok(()) + } + + pub fn on_dns_file_event(&mut self) -> Result { + loop { + let mut packet = SyscallPacket::default(); + match self.dns_file.read(&mut packet) { + Ok(0) => { + //TODO: Cleanup must occur + return Ok(false); + } + Ok(_) => (), + Err(err) => { + if err.kind() == ErrorKind::WouldBlock { + return Ok(true); + } else { + return Err(Error::from(err)); + } + } + } + // TODO: implement cancellation + let a = packet.a; + self.handle(&mut packet); + if packet.a != (-syscall::EWOULDBLOCK) as usize { + self.dns_file.write_all(&packet)?; + } else { + packet.a = a; + self.handle_block(packet)?; + } + } + } + + pub fn on_unknown_fd_event(&mut self, fd: RawFd) -> Result<()> { + trace!("Unknown fd event {}", fd); + let cur_time = libredox::call::clock_gettime(libredox::flag::CLOCK_MONOTONIC) + .map_err(|e| Error::from_syscall_error(e.into(), "Can't get time"))?; + // TODO + let cur_time = TimeSpec { + tv_sec: cur_time.tv_sec, + tv_nsec: cur_time.tv_nsec as _, + }; + + match self.domains.on_fd_event(fd, &cur_time, self.queue) { + Some(DnsParsingResult::FailFiles(fds_to_fail)) => { + for fd in &fds_to_fail { + if let Some(file) = self.files.get_mut(fd) { + *file = DnsFile::Failed; + } + } + self.wakeup_fds(&fds_to_fail); + } + Some(DnsParsingResult::WakeUpFiles(fds_to_wakeup)) => { + self.wakeup_fds(&fds_to_wakeup); + } + None => {} + } + Ok(()) + } + + pub fn on_nameserver_event(&mut self) -> Result<()> { + self.domains.update_nameserver(); + Ok(()) + } + + fn wakeup_fds(&mut self, fds_to_wakeup: &BTreeSet) { + let mut syscall_packets = vec![]; + for fd in fds_to_wakeup { + if let Some(packet) = self.wait_map.remove(fd) { + syscall_packets.push(packet); + } + } + + for mut packet in syscall_packets.drain(..) { + self.handle(&mut packet); + let _ = self.dns_file.write_all(&packet); + } + } + + fn handle_block(&mut self, packet: SyscallPacket) -> Result<()> { + let fd = packet.b; + self.wait_map.insert(fd, packet); + Ok(()) + } + + fn validate_domain(domain: &str) -> bool { + if domain.len() > 256 { + return false; + } + + for part in domain.split('.') { + if part.len() >= 63 { + return false; + } + } + + true + } +} + +impl SchemeMut for Dnsd<'_> { + fn open(&mut self, url: &str, _flags: usize, _uid: u32, _gid: u32) -> SyscallResult { + let domain = url.to_lowercase(); + if domain.is_empty() || !Dnsd::validate_domain(&domain) { + return Err(SyscallError::new(syscall::EINVAL)); + } + let fd = self.next_fd; + self.next_fd += 1; + let cur_time = libredox::call::clock_gettime(flag::CLOCK_MONOTONIC)?; + let dns_file = self.domains.file_from_domain( + &domain, + fd, + &TimeSpec { + tv_sec: cur_time.tv_sec, + tv_nsec: cur_time.tv_nsec as i32, + }, + self.queue, + ); + self.files.insert(fd, dns_file); + trace!("Open {} {}", &domain, fd); + Ok(fd) + } + + fn close(&mut self, fd: usize) -> SyscallResult { + trace!("Close {}", fd); + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + if let DnsFile::Waiting { ref domain } = *file { + self.domains.unwait_fd(domain, fd); + } + + self.files.remove(&fd); + Ok(0) + } + + fn write(&mut self, _fd: usize, _buf: &[u8]) -> SyscallResult { + Err(SyscallError::new(syscall::EINVAL)) + } + + fn read(&mut self, fd: usize, buf: &mut [u8]) -> SyscallResult { + trace!("Read {}", fd); + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + let cur_time = libredox::call::clock_gettime(flag::CLOCK_MONOTONIC)?; + + if let DnsFile::Waiting { ref domain } = *file { + *file = self.domains.file_from_domain( + domain, + fd, + &TimeSpec { + tv_sec: cur_time.tv_sec, + tv_nsec: cur_time.tv_nsec as i32, + }, + self.queue, + ); + } + + match *file { + DnsFile::Resolved { + ref data, + ref mut pos, + } => { + let mut i = 0; + while i < buf.len() && *pos < data.len() { + buf[i] = data[*pos]; + i += 1; + *pos += 1; + } + Ok(i) + } + DnsFile::Waiting { .. } => Err(SyscallError::new(syscall::EWOULDBLOCK)), + DnsFile::Timeout => Err(SyscallError::new(syscall::ETIMEDOUT)), + DnsFile::Failed => Err(SyscallError::new(syscall::ENODATA)), + } + } + + fn fevent( + &mut self, + _fd: usize, + _events: SyscallEventFlags, + ) -> SyscallResult { + Ok(SyscallEventFlags::empty()) + } + + fn fsync(&mut self, _fd: usize) -> SyscallResult { + Ok(0) + } +} diff --git a/netstack/src/lib/error.rs b/netstack/src/lib/error.rs new file mode 100644 index 0000000000..f21d3860ea --- /dev/null +++ b/netstack/src/lib/error.rs @@ -0,0 +1,66 @@ +use std::convert; +use std::fmt; +use std::io::Error as IOError; +use std::result; +use syscall::error::Error as SyscallError; + +#[derive(Debug)] +enum ErrorType { + Syscall(SyscallError), + IOError(IOError), + Other, +} + +#[derive(Debug)] +pub struct Error { + error_type: ErrorType, + descr: String, +} + +impl Error { + pub fn from_syscall_error>(syscall_error: SyscallError, descr: S) -> Error { + Error { + error_type: ErrorType::Syscall(syscall_error), + descr: descr.into(), + } + } + + pub fn from_io_error>(io_error: IOError, descr: S) -> Error { + Error { + error_type: ErrorType::IOError(io_error), + descr: descr.into(), + } + } + + pub fn other_error>(descr: S) -> Error { + Error { + error_type: ErrorType::Other, + descr: descr.into(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + match self.error_type { + ErrorType::Syscall(ref syscall_error) => { + write!(f, "{}: syscall error: {}", self.descr, syscall_error) + } + ErrorType::IOError(ref io_error) => { + write!(f, "{} : io error : {}", self.descr, io_error) + } + ErrorType::Other => { + write!(f, "{}", self.descr) + } + } + } +} +impl std::error::Error for Error {} + +impl convert::From for Error { + fn from(e: IOError) -> Self { + Error::from_io_error(e, "") + } +} + +pub type Result = result::Result; diff --git a/netstack/src/lib/lib.rs b/netstack/src/lib/lib.rs new file mode 100644 index 0000000000..f83f3f3f1c --- /dev/null +++ b/netstack/src/lib/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod logger; diff --git a/netstack/src/lib/logger.rs b/netstack/src/lib/logger.rs new file mode 100644 index 0000000000..d854b4c773 --- /dev/null +++ b/netstack/src/lib/logger.rs @@ -0,0 +1,17 @@ +use redox_log::{OutputBuilder, RedoxLogger}; + +pub fn init_logger(process_name: &str) { + if let Err(_) = RedoxLogger::new() + .with_output( + OutputBuilder::stdout() + .with_ansi_escape_codes() + .flush_on_newline(true) + .with_filter(log::LevelFilter::Trace) + .build(), + ) + .with_process_name(process_name.into()) + .enable() + { + eprintln!("{process_name}: Failed to init logger") + } +} diff --git a/netstack/src/smolnetd/buffer_pool.rs b/netstack/src/smolnetd/buffer_pool.rs new file mode 100644 index 0000000000..5991f9ece3 --- /dev/null +++ b/netstack/src/smolnetd/buffer_pool.rs @@ -0,0 +1,96 @@ +use std::cell::RefCell; +use std::mem::{replace, swap}; +use std::ops::{Deref, DerefMut, Drop}; +use std::rc::Rc; + +type BufferStack = Rc>>>; + +pub struct Buffer { + buffer: Vec, + stack: BufferStack, +} + +impl Buffer { + pub fn resize(&mut self, new_len: usize) { + self.buffer.resize(new_len, 0u8); + } + + pub fn move_out(&mut self) -> Buffer { + Buffer { + buffer: replace(&mut self.buffer, vec![]), + stack: Rc::clone(&self.stack), + } + } +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + &self.buffer + } +} + +impl AsMut<[u8]> for Buffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.buffer + } +} + +impl Deref for Buffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for Buffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + if self.buffer.capacity() > 0 { + let mut tmp = vec![]; + swap(&mut tmp, &mut self.buffer); + { + let mut stack = self.stack.borrow_mut(); + stack.push(tmp); + } + } + } +} + +pub struct BufferPool { + buffers_size: usize, + stack: BufferStack, +} + +impl BufferPool { + pub fn new(buffers_size: usize) -> BufferPool { + BufferPool { + buffers_size, + stack: Rc::new(RefCell::new(vec![])), + } + } + + pub fn get_buffer(&mut self) -> Buffer { + let buffer = match self.stack.borrow_mut().pop() { + None => vec![0u8; self.buffers_size], + Some(mut v) => { + // memsetting the buffer with `resize` would be a waste of time + let capacity = v.capacity(); + unsafe { + v.set_len(capacity); + } + v + } + }; + + Buffer { + buffer, + stack: Rc::clone(&self.stack), + } + } +} diff --git a/netstack/src/smolnetd/link/ethernet.rs b/netstack/src/smolnetd/link/ethernet.rs new file mode 100644 index 0000000000..5f32a479dd --- /dev/null +++ b/netstack/src/smolnetd/link/ethernet.rs @@ -0,0 +1,412 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::rc::Rc; + +use smoltcp::storage::PacketMetadata; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{ + ArpOperation, ArpPacket, ArpRepr, EthernetAddress, EthernetFrame, EthernetProtocol, + EthernetRepr, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr, +}; + +use super::LinkDevice; + +struct Neighbor { + hardware_address: EthernetAddress, + expires_at: Instant, +} + +#[derive(Debug, Default)] +enum ArpState { + #[default] + Discovered, + Discovering { + target: Ipv4Address, + tries: u32, + silent_until: Instant, + }, +} + +type PacketBuffer = smoltcp::storage::PacketBuffer<'static, IpAddress>; + +const EMPTY_MAC: EthernetAddress = EthernetAddress([0; 6]); + +pub struct EthernetLink { + name: Rc, + neighbor_cache: BTreeMap, + arp_state: ArpState, + waiting_packets: PacketBuffer, + input_buffer: Vec, + output_buffer: Vec, + network_file: File, + hardware_address: Option, + ip_address: Option, +} + +impl EthernetLink { + // TODO: Review these constants + const MAX_WAITING_PACKET_COUNT: usize = 10; + const MTU: usize = 1500; + const WAITING_PACKET_BUFFER_SIZE: usize = Self::MTU * Self::MAX_WAITING_PACKET_COUNT; + + const NEIGHBOR_LIVE_TIME: Duration = Duration::from_secs(60); + const ARP_SILENCE_TIME: Duration = Duration::from_secs(1); + + pub fn new(name: &str, network_file: File) -> Self { + let waiting_packets = PacketBuffer::new( + vec![PacketMetadata::EMPTY; Self::MAX_WAITING_PACKET_COUNT], + vec![0u8; Self::WAITING_PACKET_BUFFER_SIZE], + ); + + Self { + name: name.into(), + network_file, + waiting_packets, + hardware_address: None, + ip_address: None, + input_buffer: vec![0u8; Self::MTU], + output_buffer: Vec::with_capacity(Self::MTU), + arp_state: Default::default(), + neighbor_cache: Default::default(), + } + } + + fn send_to(&mut self, dst: EthernetAddress, size: usize, f: F, proto: EthernetProtocol) + where + F: FnOnce(&mut [u8]), + { + let Some(hardware_address) = self.hardware_address else { + return; + }; + + let repr = EthernetRepr { + src_addr: hardware_address, + dst_addr: dst, + ethertype: proto, + }; + + self.output_buffer.clear(); + self.output_buffer.resize(repr.buffer_len() + size, 0); + let mut frame = EthernetFrame::new_unchecked(&mut self.output_buffer); + repr.emit(&mut frame); + + f(frame.payload_mut()); + + if let Err(_) = self.network_file.write_all(&self.output_buffer) { + error!( + "Dropped outboud packet on {} (failed to write to network file)", + self.name + ) + } + } + + fn process_arp(&mut self, packet: &[u8], now: Instant) { + let Some(hardware_address) = self.hardware_address else { + return; + }; + + let Some(ip_addr) = self.ip_address else { + return; + }; + + let Ok(repr) = ArpPacket::new_checked(packet).and_then(|packet| ArpRepr::parse(&packet)) + else { + debug!("Dropped incomming arp packet on {} (Malformed)", self.name); + return; + }; + + match repr { + ArpRepr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_hardware_addr, + target_protocol_addr, + } => { + let is_unicast_mac = + target_hardware_addr != EMPTY_MAC && !target_hardware_addr.is_broadcast(); + + if is_unicast_mac && hardware_address != target_hardware_addr { + // Only process packet that are for us + return; + } + + if let ArpOperation::Unknown(_) = operation { + return; + } + + if !source_hardware_addr.is_unicast() + || source_protocol_addr.is_broadcast() + || source_protocol_addr.is_multicast() + || source_protocol_addr.is_unspecified() + { + return; + } + + if ip_addr.address() != target_protocol_addr { + return; + } + + self.neighbor_cache.insert( + IpAddress::Ipv4(source_protocol_addr), + Neighbor { + hardware_address: source_hardware_addr, + expires_at: now + Self::NEIGHBOR_LIVE_TIME, + }, + ); + + if let ArpOperation::Request = operation { + let response = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Reply, + source_hardware_addr: hardware_address, + source_protocol_addr: ip_addr.address(), + target_hardware_addr: source_hardware_addr, + target_protocol_addr: source_protocol_addr, + }; + + self.send_to( + source_hardware_addr, + response.buffer_len(), + |buf| response.emit(&mut ArpPacket::new_unchecked(buf)), + EthernetProtocol::Arp, + ); + } + self.check_waiting_packets(source_protocol_addr, source_hardware_addr, now); + } + _ => {} + } + } + + fn check_waiting_packets(&mut self, ip: Ipv4Address, mac: EthernetAddress, now: Instant) { + let mut waiting_packets = + std::mem::replace(&mut self.waiting_packets, PacketBuffer::new(vec![], vec![])); + loop { + match waiting_packets.peek() { + Ok((IpAddress::Ipv4(dst), _)) if dst == &ip => {} + Ok((IpAddress::Ipv4(dst), _)) => { + self.arp_state = ArpState::Discovering { + target: *dst, + tries: 0, + silent_until: Instant::ZERO, + }; + self.send_arp(now); + break; + } + Err(_) => { + self.arp_state = ArpState::Discovered; + break; + } + } + + let (_, packet) = waiting_packets.dequeue().unwrap(); + self.send_to( + mac, + packet.len(), + |buf| buf.copy_from_slice(packet), + EthernetProtocol::Ipv4, + ); + } + + self.waiting_packets = waiting_packets; + } + + fn drop_waiting_packets(&mut self, ip: Ipv4Address, now: Instant) { + loop { + match self.waiting_packets.peek() { + Ok((IpAddress::Ipv4(dst), _)) if dst == &ip => {} + Ok((IpAddress::Ipv4(dst), _)) => { + self.arp_state = ArpState::Discovering { + target: *dst, + tries: 0, + silent_until: Instant::ZERO, + }; + + self.send_arp(now); + + return; + } + Err(_) => { + self.arp_state = ArpState::Discovered; + return; + } + } + + let _ = self.waiting_packets.dequeue(); + debug!( + "Dropped packet on {} because neighbor was not found", + self.name + ) + } + } + + fn handle_missing_neighbor(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) { + let Ok(buf) = self.waiting_packets.enqueue(packet.len(), next_hop) else { + warn!( + "Dropped packet on {} because waiting queue was full", + self.name + ); + return; + }; + buf.copy_from_slice(packet); + + let IpAddress::Ipv4(next_hop) = next_hop; + if let ArpState::Discovered = self.arp_state { + self.arp_state = ArpState::Discovering { + target: next_hop, + tries: 0, + silent_until: Instant::ZERO, + }; + + self.send_arp(now) + } + } + + fn send_arp(&mut self, now: Instant) { + let Some(hardware_address) = self.hardware_address else { + return; + }; + + let Some(ip_address) = self.ip_address else { + return; + }; + + match self.arp_state { + ArpState::Discovered => {} + ArpState::Discovering { silent_until, .. } if silent_until > now => {} + ArpState::Discovering { target, tries, .. } if tries >= 3 => { + self.drop_waiting_packets(target, now) + } + ArpState::Discovering { + target, + ref mut tries, + ref mut silent_until, + } => { + let arp_repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: hardware_address, + source_protocol_addr: ip_address.address(), + target_hardware_addr: EthernetAddress::BROADCAST, + target_protocol_addr: target, + }; + + *tries += 1; + *silent_until = now + Self::ARP_SILENCE_TIME; + + self.send_to( + EthernetAddress::BROADCAST, + arp_repr.buffer_len(), + |buf| arp_repr.emit(&mut ArpPacket::new_unchecked(buf)), + EthernetProtocol::Arp, + ); + } + } + } +} + +impl LinkDevice for EthernetLink { + fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant) { + let local_broadcast = match self.ip_address.and_then(|cidr| cidr.broadcast()) { + Some(addr) => IpAddress::Ipv4(addr) == next_hop, + None => false, + }; + + if local_broadcast || next_hop.is_broadcast() { + self.send_to( + EthernetAddress::BROADCAST, + packet.len(), + |buf| buf.copy_from_slice(packet), + EthernetProtocol::Ipv4, + ); + return; + } + + match self.neighbor_cache.entry(next_hop) { + Entry::Vacant(_) => self.handle_missing_neighbor(next_hop, packet, now), + Entry::Occupied(e) => { + if e.get().expires_at < now { + e.remove(); + self.handle_missing_neighbor(next_hop, packet, now) + } else { + let mac = e.get().hardware_address; + self.send_to( + mac, + packet.len(), + |buf| buf.copy_from_slice(packet), + EthernetProtocol::Ipv4, + ) + } + } + } + } + + fn recv(&mut self, now: Instant) -> Option<&[u8]> { + let Some(hardware_address) = self.hardware_address else { + return None; + }; + + let mut input_buffer = std::mem::replace(&mut self.input_buffer, Vec::new()); + loop { + if let Err(e) = self.network_file.read(&mut input_buffer) { + if e.kind() != ErrorKind::WouldBlock { + error!("Failed to read ethernet device on link {}", self.name); + } else { + // No packet to read but we check if we have arp to send + self.send_arp(now); + } + self.input_buffer = input_buffer; + return None; + } + let packet = EthernetFrame::new_unchecked(&input_buffer[..]); + let Ok(repr) = EthernetRepr::parse(&packet) else { + debug!("Dropped incomming frame on {} (Malformed)", self.name); + continue; + }; + + // We let EMPTY_MAC pass because somehow this is the mac used when net=redir is used + if !repr.dst_addr.is_broadcast() + && repr.dst_addr != EMPTY_MAC + && repr.dst_addr != hardware_address + { + // Drop packets which are not for us + continue; + } + + match repr.ethertype { + EthernetProtocol::Ipv4 => { + self.input_buffer = input_buffer; + return Some(EthernetFrame::new_unchecked(&self.input_buffer[..]).payload()); + } + EthernetProtocol::Arp => self.process_arp(packet.payload(), now), + _ => continue, + } + } + } + + fn name(&self) -> &Rc { + &self.name + } + + fn can_recv(&self) -> bool { + // We don't buffer any packets so we can't receive immediatly + false + } + + fn mac_address(&self) -> Option { + self.hardware_address + } + + fn set_mac_address(&mut self, addr: EthernetAddress) { + self.hardware_address = Some(addr) + } + + fn ip_address(&self) -> Option { + Some(IpCidr::Ipv4(self.ip_address?)) + } + + fn set_ip_address(&mut self, addr: IpCidr) { + let IpCidr::Ipv4(addr) = addr; + self.ip_address = Some(addr); + } +} diff --git a/netstack/src/smolnetd/link/loopback.rs b/netstack/src/smolnetd/link/loopback.rs new file mode 100644 index 0000000000..24bdb74eba --- /dev/null +++ b/netstack/src/smolnetd/link/loopback.rs @@ -0,0 +1,63 @@ +use std::rc::Rc; + +use smoltcp::storage::PacketMetadata; +use smoltcp::time::Instant; + +use crate::scheme::Smolnetd; + +use super::LinkDevice; + +pub type PacketBuffer = smoltcp::storage::PacketBuffer<'static, ()>; + +pub struct LoopbackDevice { + name: Rc, + buffer: PacketBuffer, +} + +impl Default for LoopbackDevice { + fn default() -> Self { + let buffer = PacketBuffer::new( + vec![PacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0u8; 1500 * Smolnetd::SOCKET_BUFFER_SIZE], + ); + LoopbackDevice { + name: "loopback".into(), + buffer, + } + } +} + +impl LinkDevice for LoopbackDevice { + fn send(&mut self, _next_hop: smoltcp::wire::IpAddress, packet: &[u8], _now: Instant) { + match self.buffer.enqueue(packet.len(), ()) { + Err(_) => warn!("loopback dropped packet because buffer was full"), + Ok(buf) => buf.copy_from_slice(packet), + } + } + + fn recv(&mut self, _now: Instant) -> Option<&[u8]> { + self.buffer.dequeue().ok().map(|((), buf)| &*buf) + } + + fn name(&self) -> &std::rc::Rc { + &self.name + } + + fn can_recv(&self) -> bool { + !self.buffer.is_empty() + } + + fn mac_address(&self) -> Option { + None + } + + fn set_mac_address(&mut self, _addr: smoltcp::wire::EthernetAddress) {} + + fn ip_address(&self) -> Option { + Some("127.0.0.1/8".parse().unwrap()) + } + + fn set_ip_address(&mut self, _addr: smoltcp::wire::IpCidr) { + todo!() + } +} diff --git a/netstack/src/smolnetd/link/mod.rs b/netstack/src/smolnetd/link/mod.rs new file mode 100644 index 0000000000..e36e331ffd --- /dev/null +++ b/netstack/src/smolnetd/link/mod.rs @@ -0,0 +1,65 @@ +pub mod ethernet; +pub mod loopback; + +use std::rc::Rc; + +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; + +/// Represent a link layer device (eth0, loopback...) +pub trait LinkDevice { + /// Send the given packet to the machine with the `next_hop` ip address + /// This method cannot fail so it's the implementor responsability + /// to buffer packets which can't be sent immediatly or decide to + /// drop them if necessary + fn send(&mut self, next_hop: IpAddress, packet: &[u8], now: Instant); + + /// Returns None if nothing is received. + /// Returns an Ip packet otherwise + fn recv(&mut self, now: Instant) -> Option<&[u8]>; + + /// Returns the LinkDevice display name used to refer to it and for lookups + fn name(&self) -> &Rc; + + /// Returns wether this device have packets pending + fn can_recv(&self) -> bool; + + fn mac_address(&self) -> Option; + fn set_mac_address(&mut self, addr: EthernetAddress); + + fn ip_address(&self) -> Option; + fn set_ip_address(&mut self, addr: IpCidr); +} + +#[derive(Default)] +pub struct DeviceList { + inner: Vec>, +} + +impl DeviceList { + pub fn push(&mut self, dev: T) { + self.inner.push(Box::new(dev)) + } + + pub fn get(&self, device_name: &str) -> Option<&dyn LinkDevice> { + self.inner + .iter() + .find(|dev| dev.name().as_ref() == device_name) + .map(|device| device.as_ref()) + } + + pub fn get_mut(&mut self, device_name: &str) -> Option<&mut (dyn LinkDevice + 'static)> { + self.inner + .iter_mut() + .find(|dev| dev.name().as_ref() == device_name) + .map(|device| device.as_mut()) + } + + pub fn iter(&self) -> impl Iterator { + self.inner.iter().map(|b| b.as_ref()) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.inner.iter_mut().map(|b| b.as_mut()) + } +} diff --git a/netstack/src/smolnetd/main.rs b/netstack/src/smolnetd/main.rs new file mode 100644 index 0000000000..57d13e725c --- /dev/null +++ b/netstack/src/smolnetd/main.rs @@ -0,0 +1,175 @@ +#[macro_use] +extern crate log; +use std::process; + +use anyhow::{anyhow, bail, Context, Result}; +use event::{EventFlags, EventQueue}; +use libredox::flag::{O_CREAT, O_NONBLOCK, O_RDWR}; +use libredox::Fd; + +use redox_netstack::logger; +use scheme::Smolnetd; +use smoltcp::wire::EthernetAddress; + +mod buffer_pool; +mod link; +mod port_set; +mod router; +mod scheme; + +fn get_network_adapter() -> Result { + use std::fs; + + let mut adapters = vec![]; + + for entry_res in fs::read_dir("/scheme")? { + let Ok(entry) = entry_res else { + continue; + }; + + let Ok(scheme) = entry.file_name().into_string() else { + continue; + }; + + if !scheme.starts_with("network") { + continue; + } + + adapters.push(scheme); + } + + if adapters.is_empty() { + bail!("no network adapter found"); + } else { + let adapter = adapters.remove(0); + if !adapters.is_empty() { + // FIXME allow using multiple network adapters at the same time + warn!("Multiple network adapters found. Only {adapter} will be used"); + } + Ok(adapter) + } +} + +fn run(daemon: redox_daemon::Daemon) -> Result<()> { + let adapter = get_network_adapter()?; + trace!("opening {adapter}:"); + let network_fd = Fd::open(&format!("/scheme/{adapter}"), O_RDWR | O_NONBLOCK, 0) + .map_err(|e| anyhow!("failed to open {adapter}: {e}"))?; + + let hardware_addr = std::fs::read(format!("/scheme/{adapter}/mac")) + .map(|mac_address| EthernetAddress::from_bytes(&mac_address)) + .context("failed to get mac address from network adapter")?; + + trace!("opening :ip"); + let ip_fd = Fd::open(":ip", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :ip")?; + + trace!("opening :udp"); + let udp_fd = + Fd::open(":udp", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :udp")?; + + trace!("opening :tcp"); + let tcp_fd = + Fd::open(":tcp", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :tcp")?; + + trace!("opening :icmp"); + let icmp_fd = + Fd::open(":icmp", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :icmp")?; + + trace!("opening :netcfg"); + let netcfg_fd = + Fd::open(":netcfg", O_RDWR | O_CREAT | O_NONBLOCK, 0).context("failed to open :netcfg")?; + + let time_path = format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC); + let time_fd = Fd::open(&time_path, O_RDWR, 0).context("failed to open /scheme/time")?; + + event::user_data! { + enum EventSource { + Network, + Time, + IpScheme, + UdpScheme, + TcpScheme, + IcmpScheme, + NetcfgScheme, + } + } + + let event_queue = EventQueue::::new().context("failed to create event queue")?; + + daemon.ready().expect("smolnetd: failed to notify parent"); + + event_queue + .subscribe(network_fd.raw(), EventSource::Network, EventFlags::READ) + .context("failed to listen to network events")?; + + event_queue + .subscribe(time_fd.raw(), EventSource::Time, EventFlags::READ) + .context("failed to listen to timer events")?; + + event_queue + .subscribe(ip_fd.raw(), EventSource::IpScheme, EventFlags::READ) + .context("failed to listen to ip scheme events")?; + + event_queue + .subscribe(udp_fd.raw(), EventSource::UdpScheme, EventFlags::READ) + .context("failed to listen to udp scheme events")?; + + event_queue + .subscribe(tcp_fd.raw(), EventSource::TcpScheme, EventFlags::READ) + .context("failed to listen to tcp scheme events")?; + + event_queue + .subscribe(icmp_fd.raw(), EventSource::IcmpScheme, EventFlags::READ) + .context("failed to listen to icmp scheme events")?; + + event_queue + .subscribe(netcfg_fd.raw(), EventSource::NetcfgScheme, EventFlags::READ) + .context("failed to listen to netcfg scheme events")?; + + let mut smolnetd = Smolnetd::new( + network_fd, + hardware_addr, + ip_fd, + udp_fd, + tcp_fd, + icmp_fd, + time_fd, + netcfg_fd, + ); + + libredox::call::setrens(0, 0).context("smolnetd: failed to enter null namespace")?; + + let all = { + use EventSource::*; + [Network, Time, IpScheme, UdpScheme, IcmpScheme, NetcfgScheme].map(Ok) + }; + + for event_res in all + .into_iter() + .chain(event_queue.map(|r| r.map(|e| e.user_data))) + { + match event_res? { + EventSource::Network => smolnetd.on_network_scheme_event()?, + EventSource::Time => smolnetd.on_time_event()?, + EventSource::IpScheme => smolnetd.on_ip_scheme_event()?, + EventSource::UdpScheme => smolnetd.on_udp_scheme_event()?, + EventSource::TcpScheme => smolnetd.on_tcp_scheme_event()?, + EventSource::IcmpScheme => smolnetd.on_icmp_scheme_event()?, + EventSource::NetcfgScheme => smolnetd.on_netcfg_scheme_event()?, + } + } + Ok(()) +} + +fn main() { + redox_daemon::Daemon::new(move |daemon| { + logger::init_logger("smolnetd"); + + if let Err(err) = run(daemon) { + error!("smoltcpd: {}", err); + process::exit(1); + } + process::exit(0); + }) + .expect("smoltcp: failed to daemonize"); +} diff --git a/netstack/src/smolnetd/port_set.rs b/netstack/src/smolnetd/port_set.rs new file mode 100644 index 0000000000..88011ec337 --- /dev/null +++ b/netstack/src/smolnetd/port_set.rs @@ -0,0 +1,62 @@ +use std::collections::btree_map::{BTreeMap, Entry}; + +pub struct PortSet { + from: u16, + range: u16, + next: u16, + ports: BTreeMap, +} + +impl PortSet { + pub fn new(from: u16, to: u16) -> Option { + if from > to { + return None; + } + Some(PortSet { + from, + range: to - from + 1, + next: 0, + ports: BTreeMap::new(), + }) + } + + pub fn get_port(&mut self) -> Option { + if self.ports.len() >= self.range as usize { + return None; + } + + let port = loop { + if let Entry::Vacant(entry) = self.ports.entry(self.next) { + entry.insert(1); + let port = self.from + self.next; + self.next = self.next.wrapping_add(1); + break port; + } + self.next = self.next.wrapping_add(1); + }; + + Some(port) + } + + pub fn claim_port(&mut self, port: u16) -> bool { + if let Entry::Vacant(entry) = self.ports.entry(port) { + entry.insert(1); + true + } else { + false + } + } + + pub fn acquire_port(&mut self, port: u16) { + *self.ports.entry(port).or_insert(0) += 1; + } + + pub fn release_port(&mut self, port: u16) { + if let Entry::Occupied(mut entry) = self.ports.entry(port) { + *entry.get_mut() -= 1; + if *entry.get() == 0 { + entry.remove(); + } + } + } +} diff --git a/netstack/src/smolnetd/router/mod.rs b/netstack/src/smolnetd/router/mod.rs new file mode 100644 index 0000000000..3cfd0a2fea --- /dev/null +++ b/netstack/src/smolnetd/router/mod.rs @@ -0,0 +1,190 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use smoltcp::phy::{Device, DeviceCapabilities, Medium}; +use smoltcp::storage::PacketMetadata; +use smoltcp::time::Instant; +use smoltcp::wire::IpAddress; + +use self::route_table::RouteTable; +use crate::link::DeviceList; +use crate::scheme::Smolnetd; + +pub mod route_table; + +pub type PacketBuffer = smoltcp::storage::PacketBuffer<'static, ()>; + +pub struct Router { + rx_buffer: PacketBuffer, + tx_buffer: PacketBuffer, + devices: Rc>, + route_table: Rc>, +} + +impl Router { + pub fn new(devices: Rc>, route_table: Rc>) -> Self { + let rx_buffer = PacketBuffer::new( + vec![PacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0u8; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + let tx_buffer = PacketBuffer::new( + vec![PacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0u8; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + Self { + rx_buffer, + tx_buffer, + devices, + route_table, + } + } + + pub const MTU: usize = 1486; + + pub fn can_recv(&self) -> bool { + let mut can_recv = false; + for dev in self.devices.borrow().iter() { + can_recv |= dev.can_recv(); + } + can_recv + } + + pub fn poll(&mut self, now: Instant) { + for dev in self.devices.borrow_mut().iter_mut() { + if self.rx_buffer.is_full() { + break; + } + + loop { + if self.rx_buffer.is_full() { + break; + } + + let Some(buf) = dev.recv(now) else { + break; + }; + + self.rx_buffer + .enqueue(buf.len(), ()) + .expect("We checked if it was full") + .copy_from_slice(buf); + } + } + } + + pub fn dispatch(&mut self, now: Instant) { + while let Ok(((), packet)) = self.tx_buffer.dequeue() { + if let Ok(mut packet) = smoltcp::wire::Ipv4Packet::new_checked(packet) { + let dst_addr = IpAddress::Ipv4(packet.dst_addr()); + if packet.dst_addr().is_broadcast() { + let buf = packet.into_inner(); + for dev in self.devices.borrow_mut().iter_mut() { + dev.send(dst_addr, buf, now) + } + } else { + let route_table = self.route_table.borrow(); + let Some(rule) = route_table.lookup_rule(&dst_addr) else { + warn!("No route found for destination: {}", dst_addr); + continue; + }; + + let next_hop = match rule.via { + Some(via) => via, + None => dst_addr, + }; + + let mut devices = self.devices.borrow_mut(); + let Some(dev) = devices.get_mut(&rule.dev) else { + warn!("Device {} not found", rule.dev); + // TODO: Remove route if device doesn't exist anymore ? + continue; + }; + + let IpAddress::Ipv4(src) = rule.src; + if src != packet.src_addr() { + packet.set_src_addr(src); + packet.fill_checksum() + } + + dev.send(next_hop, packet.into_inner(), now); + } + } + } + } +} + +impl Device for Router { + type RxToken<'a> = RxToken<'a>; + + type TxToken<'a> = TxToken<'a>; + + fn receive( + &mut self, + _timestamp: smoltcp::time::Instant, + ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx_buffer.is_empty() || self.tx_buffer.is_full() { + None + } else { + Some(( + RxToken { + rx_buffer: &mut self.rx_buffer, + }, + TxToken { + tx_buffer: &mut self.tx_buffer, + }, + )) + } + } + + fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option> { + if self.tx_buffer.is_full() { + None + } else { + Some(TxToken { + tx_buffer: &mut self.tx_buffer, + }) + } + } + + fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.medium = Medium::Ip; + caps.max_transmission_unit = Router::MTU; + caps.max_burst_size = Some(Smolnetd::SOCKET_BUFFER_SIZE); + caps + } +} + +pub struct TxToken<'a> { + tx_buffer: &'a mut PacketBuffer, +} + +impl smoltcp::phy::TxToken for TxToken<'_> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(self + .tx_buffer + .enqueue(len, ()) + .expect("This was checked before creating the TxToken")) + } +} + +pub struct RxToken<'a> { + rx_buffer: &'a mut PacketBuffer, +} + +impl<'a> smoltcp::phy::RxToken for RxToken<'a> { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + let ((), buf) = self + .rx_buffer + .dequeue() + .expect("This was checked before creating the RxToken"); + + f(buf) + } +} diff --git a/netstack/src/smolnetd/router/route_table.rs b/netstack/src/smolnetd/router/route_table.rs new file mode 100644 index 0000000000..51943c932f --- /dev/null +++ b/netstack/src/smolnetd/router/route_table.rs @@ -0,0 +1,99 @@ +use std::fmt::Display; +use std::rc::Rc; + +use smoltcp::wire::{IpAddress, IpCidr}; + +#[derive(Debug)] +pub struct Rule { + pub filter: IpCidr, + pub via: Option, + pub dev: Rc, + pub src: IpAddress, +} + +impl Rule { + pub fn new(filter: IpCidr, via: Option, dev: Rc, src: IpAddress) -> Self { + Self { + filter, + via, + dev, + src, + } + } +} + +impl Display for Rule { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.filter.prefix_len() == 0 { + write!(f, "default")?; + } else { + write!(f, "{} ", self.filter)?; + } + + if let Some(via) = self.via { + write!(f, " via {}", via)?; + } + + write!(f, " dev {}", self.dev)?; + write!(f, " src {}", self.src)?; + + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct RouteTable { + rules: Vec, +} + +impl RouteTable { + pub fn lookup_rule(&self, dst: &IpAddress) -> Option<&Rule> { + self.rules + .iter() + .rev() + .find(|rule| rule.filter.contains_addr(dst)) + } + + pub fn lookup_src_addr(&self, dst: &IpAddress) -> Option { + Some(self.lookup_rule(dst)?.src) + } + + pub fn lookup_gateway(&self, dst: &IpAddress) -> Option { + self.lookup_rule(dst)?.via + } + + pub fn lookup_device(&self, dst: &IpAddress) -> Option> { + Some(self.lookup_rule(dst)?.dev.clone()) + } + + pub fn insert_rule(&mut self, new_rule: Rule) { + let i = match self + .rules + .binary_search_by_key(&new_rule.filter.prefix_len(), |rule| { + rule.filter.prefix_len() + }) { + Ok(i) | Err(i) => i, + }; + self.rules.insert(i, new_rule); + } + + pub fn remove_rule(&mut self, filter: IpCidr) { + self.rules.retain(|rule| rule.filter != filter); + } + + pub fn change_src(&mut self, old_src: IpAddress, new_src: IpAddress) { + for rule in self.rules.iter_mut().filter(|rule| rule.src == old_src) { + rule.src = new_src; + } + } +} + +impl Display for RouteTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for rule in self.rules.iter() { + writeln!(f, "{}", rule)?; + } + + Ok(()) + } +} diff --git a/netstack/src/smolnetd/scheme/icmp.rs b/netstack/src/smolnetd/scheme/icmp.rs new file mode 100644 index 0000000000..242c1fef5a --- /dev/null +++ b/netstack/src/smolnetd/scheme/icmp.rs @@ -0,0 +1,278 @@ +use smoltcp::iface::SocketHandle; +use smoltcp::socket::icmp::{ + Endpoint as IcmpEndpoint, PacketBuffer as IcmpSocketBuffer, + PacketMetadata as IcmpPacketMetadata, Socket as IcmpSocket, +}; +use smoltcp::wire::{Icmpv4Packet, Icmpv4Repr, IpAddress, IpListenEndpoint}; +use std::mem; +use std::str; +use syscall; +use syscall::{Error as SyscallError, Result as SyscallResult}; + +use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile, SocketScheme}; +use super::{Smolnetd, SocketSet}; +use crate::port_set::PortSet; +use crate::router::Router; + +pub type IcmpScheme = SocketScheme>; + +enum IcmpSocketType { + Echo, + Udp, +} + +pub struct IcmpData { + socket_type: IcmpSocketType, + ip: IpAddress, + ident: u16, +} + +impl<'a> SchemeSocket for IcmpSocket<'a> { + type SchemeDataT = PortSet; + type DataT = IcmpData; + type SettingT = (); + + fn new_scheme_data() -> Self::SchemeDataT { + PortSet::new(1u16, 0xffffu16).expect("Wrong ICMP ident values") + } + + fn can_send(&self) -> bool { + self.can_send() + } + + fn can_recv(&self) -> bool { + self.can_recv() + } + + fn may_recv(&self) -> bool { + true + } + + fn get_setting( + _file: &SocketFile, + _setting: Self::SettingT, + _buf: &mut [u8], + ) -> SyscallResult { + Ok(0) + } + + fn set_setting( + _file: &mut SocketFile, + _setting: Self::SettingT, + _buf: &[u8], + ) -> SyscallResult { + Ok(0) + } + + fn hop_limit(&self) -> u8 { + self.hop_limit().unwrap_or(64) + } + + fn set_hop_limit(&mut self, hop_limit: u8) { + self.set_hop_limit(Some(hop_limit)); + } + + fn new_socket( + socket_set: &mut SocketSet, + path: &str, + _uid: u32, + ident_set: &mut Self::SchemeDataT, + _context: &Context, + ) -> SyscallResult<(SocketHandle, Self::DataT)> { + use std::str::FromStr; + + let mut parts = path.split('/'); + let method = parts + .next() + .ok_or_else(|| syscall::Error::new(syscall::EINVAL))?; + + match method { + "echo" => { + let addr = parts + .next() + .ok_or_else(|| syscall::Error::new(syscall::EINVAL))?; + let ip = + IpAddress::from_str(addr).map_err(|_| syscall::Error::new(syscall::EINVAL))?; + + let socket = IcmpSocket::new( + IcmpSocketBuffer::new( + vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ), + IcmpSocketBuffer::new( + vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ), + ); + let handle = socket_set.add(socket); + let icmp_socket = socket_set.get_mut::(handle); + let ident = ident_set + .get_port() + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; + icmp_socket + .bind(IcmpEndpoint::Ident(ident)) + .map_err(|_| syscall::Error::new(syscall::EINVAL))?; + let socket_data = IcmpData { + socket_type: IcmpSocketType::Echo, + ident, + ip, + }; + Ok((handle, socket_data)) + } + "udp" => { + let addr = parts + .next() + .ok_or_else(|| syscall::Error::new(syscall::EINVAL))?; + let ip = + IpAddress::from_str(addr).map_err(|_| syscall::Error::new(syscall::EINVAL))?; + + let socket = IcmpSocket::new( + IcmpSocketBuffer::new( + vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ), + IcmpSocketBuffer::new( + vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ), + ); + let handle = socket_set.add(socket); + let icmp_socket = socket_set.get_mut::(handle); + let ident = ident_set + .get_port() + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; + icmp_socket + .bind(IcmpEndpoint::Udp(IpListenEndpoint::from(ident))) + .map_err(|_| syscall::Error::new(syscall::EINVAL))?; + let socket_data = IcmpData { + socket_type: IcmpSocketType::Udp, + ident, + ip, + }; + Ok((handle, socket_data)) + } + _ => Err(syscall::Error::new(syscall::EINVAL)), + } + } + + fn close_file( + &self, + file: &SchemeFile, + ident_set: &mut Self::SchemeDataT, + ) -> SyscallResult<()> { + if let SchemeFile::Socket(ref file) = *file { + ident_set.release_port(file.data.ident); + } + Ok(()) + } + + fn write_buf( + &mut self, + file: &mut SocketFile, + buf: &[u8], + ) -> SyscallResult> { + if self.can_send() { + match file.data.socket_type { + IcmpSocketType::Echo => { + if buf.len() < mem::size_of::() { + return Err(SyscallError::new(syscall::EINVAL)); + } + let (&seq_buf, payload) = buf.split_first_chunk::<2>().unwrap(); + let seq_no = u16::from_be_bytes(seq_buf); + let icmp_repr = Icmpv4Repr::EchoRequest { + ident: file.data.ident, + seq_no, + data: payload, + }; + + let icmp_payload = self + .send(icmp_repr.buffer_len(), file.data.ip) + .map_err(|_| syscall::Error::new(syscall::EINVAL))?; + let mut icmp_packet = Icmpv4Packet::new_unchecked(icmp_payload); + //TODO: replace Default with actual caps + icmp_repr.emit(&mut icmp_packet, &Default::default()); + Ok(Some(buf.len())) + } + IcmpSocketType::Udp => Err(SyscallError::new(syscall::EINVAL)), + } + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn read_buf( + &mut self, + file: &mut SocketFile, + buf: &mut [u8], + ) -> SyscallResult> { + while self.can_recv() { + let (payload, _) = self.recv().expect("Can't recv icmp packet"); + let icmp_packet = Icmpv4Packet::new_unchecked(&payload); + //TODO: replace default with actual caps + let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &Default::default()).unwrap(); + + if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr { + if buf.len() < mem::size_of::() + data.len() { + return Err(SyscallError::new(syscall::EINVAL)); + } + buf[0..2].copy_from_slice(&seq_no.to_be_bytes()); + + for i in 0..data.len() { + buf[mem::size_of::() + i] = data[i]; + } + + return Ok(Some(mem::size_of::() + data.len())); + } + } + + if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn dup( + _socket_set: &mut SocketSet, + _file: &mut SchemeFile, + _path: &str, + _: &mut Self::SchemeDataT, + ) -> SyscallResult> { + Err(SyscallError::new(syscall::EBADF)) + } + + fn fpath(&self, file: &SchemeFile, buf: &mut [u8]) -> SyscallResult { + if let SchemeFile::Socket(ref socket_file) = *file { + match socket_file.data.socket_type { + IcmpSocketType::Echo => { + let path = format!("icmp:echo/{}", socket_file.data.ip); + let path = path.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + IcmpSocketType::Udp => { + let path = format!("icmp:udp/{}", socket_file.data.ip); + let path = path.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + } + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } +} diff --git a/netstack/src/smolnetd/scheme/ip.rs b/netstack/src/smolnetd/scheme/ip.rs new file mode 100644 index 0000000000..231e22862a --- /dev/null +++ b/netstack/src/smolnetd/scheme/ip.rs @@ -0,0 +1,147 @@ +use smoltcp::iface::SocketHandle; +use smoltcp::socket::raw::{ + PacketBuffer as RawSocketBuffer, PacketMetadata as RawPacketMetadata, Socket as RawSocket, +}; +use smoltcp::wire::{IpProtocol, IpVersion}; +use std::str; +use syscall; +use syscall::{Error as SyscallError, Result as SyscallResult}; + +use crate::router::Router; + +use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile, SocketScheme}; +use super::{Smolnetd, SocketSet}; + +pub type IpScheme = SocketScheme>; + +impl<'a> SchemeSocket for RawSocket<'a> { + type SchemeDataT = (); + type DataT = (); + type SettingT = (); + + fn new_scheme_data() -> Self::SchemeDataT { + () + } + + fn can_send(&self) -> bool { + self.can_send() + } + + fn can_recv(&self) -> bool { + self.can_recv() + } + + fn may_recv(&self) -> bool { + true + } + + fn get_setting( + _file: &SocketFile, + _setting: Self::SettingT, + _buf: &mut [u8], + ) -> SyscallResult { + Ok(0) + } + + fn set_setting( + _file: &mut SocketFile, + _setting: Self::SettingT, + _buf: &[u8], + ) -> SyscallResult { + Ok(0) + } + + fn hop_limit(&self) -> u8 { + 0 + } + + fn set_hop_limit(&mut self, _hop_limit: u8) {} + + fn new_socket( + socket_set: &mut SocketSet, + path: &str, + uid: u32, + _: &mut Self::SchemeDataT, + _context: &Context, + ) -> SyscallResult<(SocketHandle, Self::DataT)> { + if uid != 0 { + return Err(SyscallError::new(syscall::EACCES)); + } + let proto = + u8::from_str_radix(path, 16).or_else(|_| Err(SyscallError::new(syscall::ENOENT)))?; + + let rx_buffer = RawSocketBuffer::new( + vec![RawPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + let tx_buffer = RawSocketBuffer::new( + vec![RawPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + let ip_socket = RawSocket::new( + IpVersion::Ipv4, + IpProtocol::from(proto), + rx_buffer, + tx_buffer, + ); + + let socket_handle = socket_set.add(ip_socket); + Ok((socket_handle, ())) + } + + fn close_file(&self, _: &SchemeFile, _: &mut Self::SchemeDataT) -> SyscallResult<()> { + Ok(()) + } + + fn write_buf( + &mut self, + file: &mut SocketFile, + buf: &[u8], + ) -> SyscallResult> { + if self.can_send() { + self.send_slice(buf).expect("Can't send slice"); + Ok(Some(buf.len())) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn read_buf( + &mut self, + file: &mut SocketFile, + buf: &mut [u8], + ) -> SyscallResult> { + if self.can_recv() { + let length = self.recv_slice(buf).expect("Can't receive slice"); + Ok(Some(length)) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn dup( + _socket_set: &mut SocketSet, + _file: &mut SchemeFile, + _path: &str, + _: &mut Self::SchemeDataT, + ) -> SyscallResult> { + Err(SyscallError::new(syscall::EBADF)) + } + + fn fpath(&self, _file: &SchemeFile, buf: &mut [u8]) -> SyscallResult { + let path = format!("ip:{}", self.ip_protocol()); + let path = path.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } +} diff --git a/netstack/src/smolnetd/scheme/mod.rs b/netstack/src/smolnetd/scheme/mod.rs new file mode 100644 index 0000000000..789efc07c1 --- /dev/null +++ b/netstack/src/smolnetd/scheme/mod.rs @@ -0,0 +1,292 @@ +use crate::link::ethernet::EthernetLink; +use crate::link::LinkDevice; +use crate::link::{loopback::LoopbackDevice, DeviceList}; +use crate::router::route_table::{RouteTable, Rule}; +use crate::router::Router; +use crate::scheme::smoltcp::iface::SocketSet as SmoltcpSocketSet; +use libredox::Fd; +use netutils::getcfg; +use smoltcp; +use smoltcp::iface::{Config, Interface as SmoltcpInterface}; +use smoltcp::phy::Tracer; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{ + EthernetAddress, HardwareAddress, IpAddress, IpCidr, IpListenEndpoint, Ipv4Address, +}; +use std::cell::RefCell; +use std::fs::File; +use std::io::{Read, Write}; +use std::mem::size_of; +use std::os::fd::{FromRawFd, RawFd}; +use std::rc::Rc; +use std::str::FromStr; +use syscall; +use syscall::data::TimeSpec; + +use self::icmp::IcmpScheme; +use self::ip::IpScheme; +use self::netcfg::NetCfgScheme; +use self::tcp::TcpScheme; +use self::udp::UdpScheme; +use redox_netstack::error::{Error, Result}; + +mod icmp; +mod ip; +mod netcfg; +mod socket; +mod tcp; +mod udp; + +type SocketSet = SmoltcpSocketSet<'static>; +type Interface = Rc>; + +const MAX_DURATION: Duration = Duration::from_micros(u64::MAX); +const MIN_DURATION: Duration = Duration::from_micros(0); + +pub struct Smolnetd { + router_device: Tracer, + iface: Interface, + time_file: File, + + socket_set: Rc>, + timer: ::std::time::Instant, + + ip_scheme: IpScheme, + udp_scheme: UdpScheme, + tcp_scheme: TcpScheme, + icmp_scheme: IcmpScheme, + netcfg_scheme: NetCfgScheme, +} + +impl Smolnetd { + pub const MAX_PACKET_SIZE: usize = 2048; + pub const SOCKET_BUFFER_SIZE: usize = 128; //packets + pub const MIN_CHECK_TIMEOUT: Duration = Duration::from_millis(10); + pub const MAX_CHECK_TIMEOUT: Duration = Duration::from_millis(500); + + pub fn new( + network_file: Fd, + hardware_addr: EthernetAddress, + ip_file: Fd, + udp_file: Fd, + tcp_file: Fd, + icmp_file: Fd, + time_file: Fd, + netcfg_file: Fd, + ) -> Smolnetd { + let protocol_addrs = vec![ + //This is a placeholder IP for DHCP + IpCidr::new(IpAddress::v4(0, 0, 0, 0), 8), + ]; + + let default_gw = Ipv4Address::from_str(getcfg("ip_router").unwrap().trim()) + .expect("Can't parse the 'ip_router' cfg."); + + let devices = Rc::new(RefCell::new(DeviceList::default())); + let route_table = Rc::new(RefCell::new(RouteTable::default())); + let mut network_device = Tracer::new( + Router::new(Rc::clone(&devices), Rc::clone(&route_table)), + |_timestamp, printer| trace!("{}", printer), + ); + + let config = Config::new(HardwareAddress::Ip); + let mut iface = SmoltcpInterface::new(config, &mut network_device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| ip_addrs.extend(protocol_addrs)); + iface + .routes_mut() + .add_default_ipv4_route(default_gw) + .expect("Failed to add default gateway"); + + let iface = Rc::new(RefCell::new(iface)); + let socket_set = Rc::new(RefCell::new(SocketSet::new(vec![]))); + + let loopback = LoopbackDevice::default(); + route_table.borrow_mut().insert_rule(Rule::new( + "127.0.0.0/8".parse().unwrap(), + None, + Rc::clone(loopback.name()), + "127.0.0.1".parse().unwrap(), + )); + + let mut eth0 = EthernetLink::new("eth0", unsafe { + File::from_raw_fd(network_file.into_raw() as RawFd) + }); + eth0.set_mac_address(hardware_addr); + + devices.borrow_mut().push(loopback); + devices.borrow_mut().push(eth0); + + Smolnetd { + iface: Rc::clone(&iface), + router_device: network_device, + socket_set: Rc::clone(&socket_set), + timer: ::std::time::Instant::now(), + time_file: unsafe { File::from_raw_fd(time_file.into_raw() as RawFd) }, + ip_scheme: IpScheme::new( + Rc::clone(&iface), + Rc::clone(&route_table), + Rc::clone(&socket_set), + unsafe { File::from_raw_fd(ip_file.into_raw() as RawFd) }, + ), + udp_scheme: UdpScheme::new( + Rc::clone(&iface), + Rc::clone(&route_table), + Rc::clone(&socket_set), + unsafe { File::from_raw_fd(udp_file.into_raw() as RawFd) }, + ), + tcp_scheme: TcpScheme::new( + Rc::clone(&iface), + Rc::clone(&route_table), + Rc::clone(&socket_set), + unsafe { File::from_raw_fd(tcp_file.into_raw() as RawFd) }, + ), + icmp_scheme: IcmpScheme::new( + Rc::clone(&iface), + Rc::clone(&route_table), + Rc::clone(&socket_set), + unsafe { File::from_raw_fd(icmp_file.into_raw() as RawFd) }, + ), + netcfg_scheme: NetCfgScheme::new( + Rc::clone(&iface), + unsafe { File::from_raw_fd(netcfg_file.into_raw() as RawFd) }, + Rc::clone(&route_table), + Rc::clone(&devices), + ), + } + } + + pub fn on_network_scheme_event(&mut self) -> Result<()> { + self.poll()?; + Ok(()) + } + + pub fn on_ip_scheme_event(&mut self) -> Result<()> { + self.ip_scheme.on_scheme_event()?; + let _ = self.poll()?; + Ok(()) + } + + pub fn on_udp_scheme_event(&mut self) -> Result<()> { + self.udp_scheme.on_scheme_event()?; + let _ = self.poll()?; + Ok(()) + } + + pub fn on_tcp_scheme_event(&mut self) -> Result<()> { + self.tcp_scheme.on_scheme_event()?; + let _ = self.poll()?; + Ok(()) + } + + pub fn on_icmp_scheme_event(&mut self) -> Result<()> { + self.icmp_scheme.on_scheme_event()?; + let _ = self.poll()?; + Ok(()) + } + + pub fn on_time_event(&mut self) -> Result<()> { + let timeout = self.poll()?; + self.schedule_time_event(timeout)?; + //TODO: Fix network scheme to ensure events are not missed + self.on_network_scheme_event() + } + + pub fn on_netcfg_scheme_event(&mut self) -> Result<()> { + self.netcfg_scheme.on_scheme_event()?; + Ok(()) + } + + fn schedule_time_event(&mut self, timeout: Duration) -> Result<()> { + let mut time = TimeSpec::default(); + if self.time_file.read(&mut time)? < size_of::() { + return Err(Error::from_syscall_error( + syscall::Error::new(syscall::EBADF), + "Can't read current time", + )); + } + let mut time_ms = time.tv_sec * 1000i64 + i64::from(time.tv_nsec) / 1_000_000i64; + time_ms += timeout.total_millis() as i64; + time.tv_sec = time_ms / 1000; + time.tv_nsec = ((time_ms % 1000) * 1_000_000) as i32; + self.time_file + .write_all(&time) + .map_err(|e| Error::from_io_error(e, "Failed to write to time file"))?; + Ok(()) + } + + fn poll(&mut self) -> Result { + let timeout = { + let mut iter_limit = 10usize; + let mut iface = self.iface.borrow_mut(); + let mut socket_set = self.socket_set.borrow_mut(); + + loop { + let timestamp = Instant::from(self.timer); + if iter_limit == 0 { + break MIN_DURATION; + } + iter_limit -= 1; + + self.router_device.get_mut().poll(timestamp); + + // TODO: Check what if the bool returned by poll can be useful + iface.poll(timestamp, &mut self.router_device, &mut socket_set); + + self.router_device.get_mut().dispatch(timestamp); + + if !self.router_device.get_ref().can_recv() { + match iface.poll_delay(timestamp, &socket_set) { + Some(delay) if delay == Duration::ZERO => {} + Some(delay) => break ::std::cmp::min(MAX_DURATION, delay), + None => break MAX_DURATION, + }; + } + } + }; + + self.notify_sockets()?; + + Ok(::std::cmp::min( + ::std::cmp::max(Smolnetd::MIN_CHECK_TIMEOUT, timeout), + Smolnetd::MAX_CHECK_TIMEOUT, + )) + } + + fn notify_sockets(&mut self) -> Result<()> { + self.ip_scheme.notify_sockets()?; + self.udp_scheme.notify_sockets()?; + self.tcp_scheme.notify_sockets()?; + self.icmp_scheme.notify_sockets() + } +} + +fn post_fevent(scheme_file: &mut File, fd: usize, event: usize, data_len: usize) -> Result<()> { + scheme_file + .write(&syscall::Packet { + id: 0, + pid: 0, + uid: 0, + gid: 0, + a: syscall::number::SYS_FEVENT, + b: fd, + c: event, + d: data_len, + }) + .map(|_| ()) + .map_err(|e| Error::from_io_error(e, "failed to post fevent")) +} + +fn parse_endpoint(socket: &str) -> IpListenEndpoint { + let mut socket_parts = socket.split(':'); + let host = Ipv4Address::from_str(socket_parts.next().unwrap_or("")) + .ok() + .filter(|addr| !addr.is_unspecified()) + .map(IpAddress::Ipv4); + + let port = socket_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + IpListenEndpoint { addr: host, port } +} diff --git a/netstack/src/smolnetd/scheme/netcfg/mod.rs b/netstack/src/smolnetd/scheme/netcfg/mod.rs new file mode 100644 index 0000000000..846bcddfdb --- /dev/null +++ b/netstack/src/smolnetd/scheme/netcfg/mod.rs @@ -0,0 +1,539 @@ +#[macro_use] +mod nodes; +mod notifier; + +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::mem; +use std::rc::Rc; +use std::str; +use std::str::FromStr; +use syscall; +use syscall::data::Stat; +use syscall::flag::{MODE_DIR, MODE_FILE}; +use syscall::{ + Error as SyscallError, EventFlags as SyscallEventFlags, Packet as SyscallPacket, + Result as SyscallResult, SchemeMut, +}; + +use crate::link::DeviceList; +use crate::router::route_table::{RouteTable, Rule}; + +use self::nodes::*; +use self::notifier::*; +use super::{post_fevent, Interface}; +use redox_netstack::error::{Error, Result}; + +const WRITE_BUFFER_MAX_SIZE: usize = 0xffff; + +fn gateway_cidr() -> IpCidr { + // TODO: const fn + IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0) +} + +fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult { + let mut parts = value.split_whitespace(); + let cidr_str = parts.next().ok_or(SyscallError::new(syscall::EINVAL))?; + let cidr = match cidr_str { + "default" => gateway_cidr(), + cidr_str => cidr_str + .parse() + .map_err(|_| SyscallError::new(syscall::EINVAL))?, + }; + + let via: IpAddress = match parts.next().ok_or(SyscallError::new(syscall::EINVAL))? { + "via" => parts + .next() + .ok_or(SyscallError::new(syscall::EINVAL))? + .parse() + .map_err(|_| SyscallError::new(syscall::EINVAL))?, + _ => return Err(SyscallError::new(syscall::EINVAL)), + }; + + if !via.is_unicast() { + return Err(SyscallError::new(syscall::EINVAL)); + } + + let rule = route_table + .lookup_rule(&via) + .ok_or(SyscallError::new(syscall::EINVAL))?; + + Ok(Rule::new(cidr, Some(via), rule.dev.clone(), rule.src)) +} + +fn mk_root_node( + iface: Interface, + notifier: NotifierRef, + dns_config: DNSConfigRef, + route_table: Rc>, + devices: Rc>, +) -> CfgNodeRef { + cfg_node! { + "resolv" => { + "nameserver" => { + rw [dns_config, notifier] (Option, None) + || { + format!("{}\n", dns_config.borrow().name_server) + } + |cur_value, line| { + if cur_value.is_none() { + let ip = Ipv4Address::from_str(line.trim()) + .map_err(|_| SyscallError::new(syscall::EINVAL))?; + if ip.is_broadcast() || ip.is_multicast() || ip.is_unspecified() { + return Err(SyscallError::new(syscall::EINVAL)); + } + *cur_value = Some(ip); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + |cur_value| { + if let Some(ip) = *cur_value { + dns_config.borrow_mut().name_server = ip; + notifier.borrow_mut().schedule_notify("resolv/nameserver"); + } + Ok(()) + } + } + }, + "route" => { + "list" => { + ro [route_table] || { + format!("{}", route_table.borrow()) + } + }, + "add" => { + wo [iface, notifier, route_table] (Option, None) + |cur_value, line| { + if cur_value.is_none() { + let route = parse_route(line, &route_table.borrow())?; + *cur_value = Some(route); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + |cur_value| { + if let Some(route) = cur_value.take() { + route_table.borrow_mut().insert_rule(route); + notifier.borrow_mut().schedule_notify("route/list"); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + }, + "rm" => { + wo [iface, notifier, route_table] (Option, None) + |cur_value, line| { + if cur_value.is_none() { + match line.parse() { + Ok(cidr) => { + *cur_value = Some(cidr); + Ok(()) + } + Err(_) => Err(SyscallError::new(syscall::EINVAL)) + } + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + |cur_value| { + if let Some(cidr) = *cur_value { + route_table.borrow_mut().remove_rule(cidr); + notifier.borrow_mut().schedule_notify("route/list"); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + }, + }, + "ifaces" => { + "eth0" => { + "mac" => { + rw [iface, notifier, devices] (Option, None) + || { + match devices.borrow().get("eth0") { + Some(dev) => { + match dev.mac_address() { + Some(addr) => format!("{addr}\n"), + None => "Not configured\n".into(), + } + } + None => "Device not found\n".into(), + } + } + |cur_value, line| { + if cur_value.is_none() { + let mac = EthernetAddress::from_str(line). + map_err(|_| SyscallError::new(syscall::EINVAL))?; + if !mac.is_unicast() { + return Err(SyscallError::new(syscall::EINVAL)); + } + *cur_value = Some(mac); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + |cur_value| { + if let Some(mac) = *cur_value { + if let Some(dev) = devices.borrow_mut().get_mut("eth0") { + dev.set_mac_address(mac); + notifier.borrow_mut().schedule_notify("ifaces/eth0/mac"); + } + } + Ok(()) + } + }, + "addr" => { + "list" => { + ro [devices] + || { + let res = match devices.borrow().get("eth0") { + Some(dev) => { + match dev.ip_address() { + Some(addr) => format!("{addr}\n"), + None => "Not configured\n".into(), + } + } + None => "Device not found\n".into(), + }; + res + } + }, + "set" => { + wo [iface, notifier, devices, route_table] (Option, None) + |cur_value, line| { + if cur_value.is_none() { + let cidr = IpCidr::from_str(line) + .map_err(|_| SyscallError::new(syscall::EINVAL))?; + if !cidr.address().is_unicast() { + return Err(SyscallError::new(syscall::EINVAL)); + } + *cur_value = Some(cidr); + Ok(()) + } else { + Err(SyscallError::new(syscall::EINVAL)) + } + } + |cur_value| { + // TODO: Multiple IPs + if let Some(cidr) = cur_value.take() { + if let Some(dev) = devices.borrow_mut().get_mut("eth0") { + + let mut route_table = route_table.borrow_mut(); + if let Some(old_addr) = dev.ip_address() { + let IpCidr::Ipv4(old_v4_cidr) = old_addr; + let old_network = IpCidr::Ipv4(old_v4_cidr.network()); + + route_table.remove_rule(old_network); + route_table.change_src(old_addr.address(), cidr.address()); + iface.borrow_mut().update_ip_addrs(|addrs| addrs.retain(|addr| *addr != old_addr)) + } + + dev.set_ip_address(cidr); + // FIXME: Here, the insert 0 is a workaround to let UDP sockets + // work with this interface only. + // Smoltcp takes the first ip address when looking for a source + // ip address when sending UDP packets. + // This behavior will have to be fixed as it's our route table + // job to find give this source. + iface.borrow_mut().update_ip_addrs(|addrs| addrs.insert(0, cidr).unwrap()); + + let IpCidr::Ipv4(v4_cidr) = cidr; + let network_cidr = IpCidr::Ipv4(v4_cidr.network()); + route_table.insert_rule(Rule::new(network_cidr, None, dev.name().clone(), cidr.address())) + } + notifier.borrow_mut().schedule_notify("ifaces/eth0/addr/list"); + notifier.borrow_mut().schedule_notify("route/list"); + } + Ok(()) + } + }, + } + } + } + } +} + +struct DNSConfig { + name_server: Ipv4Address, +} + +type DNSConfigRef = Rc>; + +struct NetCfgFile { + path: String, + is_dir: bool, + is_writable: bool, + is_readable: bool, + node_writer: Option>, + read_buf: Vec, + write_buf: Vec, + pos: usize, + uid: u32, + done: bool, +} + +impl NetCfgFile { + fn commit(&mut self) -> SyscallResult<()> { + if let Some(ref mut node_writer) = self.node_writer { + if !self.write_buf.is_empty() { + let line = str::from_utf8(&self.write_buf) + .or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?; + node_writer.write_line(line)?; + } + node_writer.commit()?; + self.write_buf.clear(); + } + Ok(()) + } + + fn consume_lines(&mut self) -> SyscallResult<()> { + if let Some(ref mut node_writer) = self.node_writer { + let mut swap_with = None; + { + let mut lines = self.write_buf.split(|&c| c == b'\n'); + if let Some(mut cur_line) = lines.next() { + let mut consumed = false; + for next_line in lines { + let line = str::from_utf8(cur_line) + .or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?; + trace!("writing line {}", line); + node_writer.write_line(line)?; + cur_line = next_line; + consumed = true; + } + if consumed { + swap_with = Some(From::from(cur_line)) + } + } + } + if let Some(ref mut new_vec) = swap_with { + mem::swap(&mut self.write_buf, new_vec); + } + Ok(()) + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } +} + +pub struct NetCfgScheme { + scheme_file: File, + next_fd: usize, + files: BTreeMap, + root_node: CfgNodeRef, + notifier: NotifierRef, +} + +impl NetCfgScheme { + pub fn new( + iface: Interface, + scheme_file: File, + route_table: Rc>, + devices: Rc>, + ) -> NetCfgScheme { + let notifier = Notifier::new_ref(); + let dns_config = Rc::new(RefCell::new(DNSConfig { + name_server: Ipv4Address::new(8, 8, 8, 8), + })); + NetCfgScheme { + scheme_file, + next_fd: 1, + files: BTreeMap::new(), + root_node: mk_root_node( + iface, + Rc::clone(¬ifier), + dns_config, + route_table, + devices, + ), + notifier, + } + } + + pub fn on_scheme_event(&mut self) -> Result> { + let result = loop { + let mut packet = SyscallPacket::default(); + match self.scheme_file.read(&mut packet) { + Ok(0) => { + //TODO: Cleanup must occur + break Some(()); + } + Ok(_) => (), + Err(err) => { + if err.kind() == ErrorKind::WouldBlock { + break None; + } else { + return Err(Error::from(err)); + } + } + } + self.handle(&mut packet); + self.scheme_file.write_all(&packet)?; + }; + self.notify_scheduled_fds(); + Ok(result) + } + + fn notify_scheduled_fds(&mut self) { + let fds_to_notify = self.notifier.borrow_mut().get_notified_fds(); + for fd in fds_to_notify { + let _ = post_fevent(&mut self.scheme_file, fd, syscall::EVENT_READ.bits(), 1); + } + } +} + +impl SchemeMut for NetCfgScheme { + fn open(&mut self, path: &str, _flags: usize, uid: u32, _gid: u32) -> SyscallResult { + let mut current_node = Rc::clone(&self.root_node); + for part in path.split('/') { + if part.is_empty() { + continue; + } + let next_node = current_node + .borrow_mut() + .open(part) + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; + current_node = next_node; + } + let current_node = current_node.borrow(); + let read_buf = Vec::from(current_node.read()); + let fd = self.next_fd; + trace!("open {} {}", fd, path); + self.next_fd += 1; + self.files.insert( + fd, + NetCfgFile { + path: path.to_owned(), + is_dir: current_node.is_dir(), + is_writable: current_node.is_writable(), + is_readable: current_node.is_readable(), + node_writer: if current_node.is_writable() { + current_node.new_writer() + } else { + None + }, + uid, + pos: 0, + read_buf, + write_buf: vec![], + done: false, + }, + ); + Ok(fd) + } + + fn close(&mut self, fd: usize) -> SyscallResult { + trace!("close {}", fd); + if let Some(mut file) = self.files.remove(&fd) { + self.notifier.borrow_mut().unsubscribe(&file.path, fd); + if !file.done { + file.commit().map(|_| 0) + } else { + Ok(0) + } + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } + + fn write(&mut self, fd: usize, buf: &[u8]) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + if file.done { + return Err(SyscallError::new(syscall::EBADF)); + } + + if file.uid != 0 { + return Err(SyscallError::new(syscall::EACCES)); + } + + if (WRITE_BUFFER_MAX_SIZE - file.write_buf.len()) < buf.len() { + return Err(SyscallError::new(syscall::EMSGSIZE)); + } + + file.write_buf.extend_from_slice(buf); + + if let Err(e) = file.consume_lines() { + trace!("Failed write {} {}", fd, e); + file.done = true; + return Err(e); + } + + Ok(buf.len()) + } + + fn read(&mut self, fd: usize, buf: &mut [u8]) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + let mut i = 0; + while i < buf.len() && file.pos < file.read_buf.len() { + buf[i] = file.read_buf[file.pos]; + i += 1; + file.pos += 1; + } + Ok(i) + } + + fn fstat(&mut self, fd: usize, stat: &mut Stat) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + stat.st_mode = if file.is_dir { MODE_DIR } else { MODE_FILE }; + if file.is_writable { + stat.st_mode |= 0o222; + } + if file.is_readable { + stat.st_mode |= 0o444; + } + stat.st_uid = 0; + stat.st_gid = 0; + stat.st_size = file.read_buf.len() as u64; + + Ok(0) + } + + fn fevent(&mut self, fd: usize, events: SyscallEventFlags) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + if events.contains(syscall::EVENT_READ) { + self.notifier.borrow_mut().subscribe(&file.path, fd); + } else { + self.notifier.borrow_mut().unsubscribe(&file.path, fd); + } + Ok(SyscallEventFlags::empty()) + } + + fn fsync(&mut self, fd: usize) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + if !file.done { + let res = file.commit().map(|_| 0); + file.done = true; + res + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } +} diff --git a/netstack/src/smolnetd/scheme/netcfg/nodes.rs b/netstack/src/smolnetd/scheme/netcfg/nodes.rs new file mode 100644 index 0000000000..4183da73cd --- /dev/null +++ b/netstack/src/smolnetd/scheme/netcfg/nodes.rs @@ -0,0 +1,270 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::rc::Rc; +use syscall::Result as SyscallResult; + +pub type CfgNodeRef = Rc>; + +pub trait NodeWriter { + fn write_line(&mut self, _: &str) -> SyscallResult<()> { + Ok(()) + } + + fn commit(&mut self) -> SyscallResult<()> { + Ok(()) + } +} + +pub struct SimpleWriter +where + WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>, + C: 'static + Fn(&mut T) -> SyscallResult<()>, +{ + data: T, + write_line: WL, + commit: C, +} + +impl NodeWriter for SimpleWriter +where + WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>, + C: 'static + Fn(&mut T) -> SyscallResult<()>, +{ + fn write_line(&mut self, line: &str) -> SyscallResult<()> { + (self.write_line)(&mut self.data, line) + } + + fn commit(&mut self) -> SyscallResult<()> { + (self.commit)(&mut self.data) + } +} + +impl SimpleWriter +where + WL: 'static + Fn(&mut T, &str) -> SyscallResult<()>, + C: 'static + Fn(&mut T) -> SyscallResult<()>, +{ + pub fn new_boxed(data: T, write_line: WL, commit: C) -> Box { + Box::new(SimpleWriter { + data, + write_line, + commit, + }) + } +} + +pub trait CfgNode { + fn is_dir(&self) -> bool { + false + } + + fn is_writable(&self) -> bool { + false + } + + fn is_readable(&self) -> bool { + true + } + + fn read(&self) -> String { + String::new() + } + + fn open(&self, _file: &str) -> Option { + None + } + + fn new_writer(&self) -> Option> { + None + } +} + +pub struct RONode +where + F: Fn() -> String, +{ + read_fun: F, +} + +impl CfgNode for RONode +where + F: Fn() -> String, +{ + fn read(&self) -> String { + (self.read_fun)() + } +} + +impl RONode +where + F: 'static + Fn() -> String, +{ + pub fn new_ref(read_fun: F) -> CfgNodeRef { + Rc::new(RefCell::new(RONode { read_fun })) + } +} + +pub struct WONode +where + W: 'static + Fn() -> Box, +{ + new_writer: W, +} + +impl CfgNode for WONode +where + W: 'static + Fn() -> Box, +{ + fn is_readable(&self) -> bool { + false + } + + fn is_writable(&self) -> bool { + true + } + + fn new_writer(&self) -> Option> { + Some((self.new_writer)()) + } +} + +impl WONode +where + W: 'static + Fn() -> Box, +{ + pub fn new_ref(new_writer: W) -> CfgNodeRef { + Rc::new(RefCell::new(WONode { new_writer })) + } +} + +pub struct RWNode +where + F: Fn() -> String, + W: 'static + Fn() -> Box, +{ + read_fun: F, + new_writer: W, +} + +impl CfgNode for RWNode +where + F: Fn() -> String, + W: 'static + Fn() -> Box, +{ + fn read(&self) -> String { + (self.read_fun)() + } + + fn is_writable(&self) -> bool { + true + } + + fn new_writer(&self) -> Option> { + Some((self.new_writer)()) + } +} + +impl RWNode +where + F: 'static + Fn() -> String, + W: 'static + Fn() -> Box, +{ + pub fn new_ref(read_fun: F, new_writer: W) -> CfgNodeRef { + Rc::new(RefCell::new(RWNode { + read_fun, + new_writer, + })) + } +} + +pub struct StaticDirNode { + child_nodes: BTreeMap, +} + +impl CfgNode for StaticDirNode { + fn is_dir(&self) -> bool { + true + } + + fn read(&self) -> String { + let mut files = String::new(); + for child in self.child_nodes.keys() { + if !files.is_empty() { + files.push('\n'); + } + files += child; + } + files + } + + fn open(&self, file: &str) -> Option { + self.child_nodes.get(file).map(|node| Rc::clone(node)) + } +} + +impl StaticDirNode { + pub fn new_ref(child_nodes: BTreeMap) -> CfgNodeRef { + Rc::new(RefCell::new(StaticDirNode { child_nodes })) + } +} + +macro_rules! cfg_node { + (val $e:expr) => { + $e + }; + (ro [ $($c:ident),* ] || $b:block ) => { + { + $(let $c = $c.clone();)* + RONode::new_ref(move|| $b) + } + }; + (wo [ $($c:ident),* ] ( $et:ty , $e:expr ) |$data_i:ident, $line_i:ident| + $write_line:block |$data_i2:ident| $commit:block) => { + { + $(#[allow(unused_variables)] let $c = $c.clone();)* + let new_writer = move || -> Box { + let write_line = { + $(#[allow(unused_variables)] let $c = $c.clone();)* + move |$data_i: &mut $et, $line_i: &str| $write_line + }; + let commit = { + $(#[allow(unused_variables)] let $c = $c.clone();)* + move |$data_i2: &mut $et| $commit + }; + let data: $et = $e; + SimpleWriter::new_boxed(data, write_line, commit) + }; + WONode::new_ref(new_writer) + } + }; + (rw [ $($c:ident),* ] ( $et:ty , $e:expr ) || $read_fun:block |$data_i:ident, $line_i:ident| + $write_line:block |$data_i2:ident| $commit:block) => { + { + let read_fun = { + $(#[allow(unused_variables)] let $c = $c.clone();)* + move || $read_fun + }; + $(#[allow(unused_variables)] let $c = $c.clone();)* + let new_writer = move || -> Box { + let write_line = { + $(#[allow(unused_variables)] let $c = $c.clone();)* + move |$data_i: &mut $et, $line_i: &str| $write_line + }; + let commit = { + $(#[allow(unused_variables)] let $c = $c.clone();)* + move |$data_i2: &mut $et| $commit + }; + let data: $et = $e; + SimpleWriter::new_boxed(data, write_line, commit) + }; + RWNode::new_ref(read_fun, new_writer) + } + }; + ($($e:expr => { $($t:tt)* }),* $(,)*) => { + { + let mut children = BTreeMap::new(); + $(children.insert($e.into(), cfg_node!($($t)*));)* + StaticDirNode::new_ref(children) + } + }; +} diff --git a/netstack/src/smolnetd/scheme/netcfg/notifier.rs b/netstack/src/smolnetd/scheme/netcfg/notifier.rs new file mode 100644 index 0000000000..8f05a92d1f --- /dev/null +++ b/netstack/src/smolnetd/scheme/netcfg/notifier.rs @@ -0,0 +1,62 @@ +use std::cell::RefCell; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet}; +use std::rc::Rc; + +pub struct Notifier { + listeners: BTreeMap>, + notified: BTreeSet, +} + +pub type NotifierRef = Rc>; + +impl Notifier { + pub fn new_ref() -> NotifierRef { + Rc::new(RefCell::new(Notifier { + listeners: BTreeMap::new(), + notified: BTreeSet::new(), + })) + } + + pub fn subscribe(&mut self, path: &str, fd: usize) { + trace!("Sub fd {} to {}", fd, path); + match self.listeners.entry(path.to_owned()) { + Entry::Occupied(mut e) => { + e.get_mut().insert(fd); + } + Entry::Vacant(e) => { + let mut fds = BTreeSet::new(); + fds.insert(fd); + e.insert(fds); + } + } + } + + pub fn unsubscribe(&mut self, path: &str, fd: usize) { + let empty = if let Some(fds) = self.listeners.get_mut(path) { + if fds.remove(&fd) { + trace!("Unsub fd {} from {}", fd, path); + } + fds.is_empty() + } else { + false + }; + if empty { + self.listeners.remove(path); + } + } + + pub fn schedule_notify(&mut self, path: &str) { + trace!("Notifying {}", path); + if let Some(fds) = self.listeners.get(path) { + self.notified.extend(fds); + } + } + + pub fn get_notified_fds(&mut self) -> BTreeSet { + use std::mem::swap; + let mut notified = BTreeSet::new(); + swap(&mut self.notified, &mut notified); + notified + } +} diff --git a/netstack/src/smolnetd/scheme/socket.rs b/netstack/src/smolnetd/scheme/socket.rs new file mode 100644 index 0000000000..4ba2a27d6e --- /dev/null +++ b/netstack/src/smolnetd/scheme/socket.rs @@ -0,0 +1,837 @@ +use std::cell::RefCell; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ops::DerefMut; +use std::rc::Rc; +use std::str; + +use libredox::flag::{self, CLOCK_MONOTONIC}; +use syscall::data::TimeSpec; +use syscall::flag::{EVENT_READ, EVENT_WRITE}; +use syscall::{self, KSMSG_CANCEL}; +use syscall::{ + Error as SyscallError, EventFlags as SyscallEventFlags, Packet as SyscallPacket, + Result as SyscallResult, SchemeBlockMut, +}; + +use super::Interface; +use crate::router::route_table::RouteTable; +use crate::scheme::smoltcp::iface::SocketHandle; +use redox_netstack::error::{Error, Result}; +use smoltcp::socket::AnySocket; + +use super::{post_fevent, SocketSet}; + +pub struct Context { + pub iface: Interface, + pub route_table: Rc>, +} + +pub struct NullFile { + pub flags: usize, + pub uid: u32, + pub gid: u32, +} + +pub struct SocketFile { + pub flags: usize, + pub data: DataT, + + events: usize, + socket_handle: SocketHandle, + read_notified: bool, + write_notified: bool, + read_timeout: Option, + write_timeout: Option, +} + +impl SocketFile { + pub fn clone_with_data(&self, data: DataT) -> SocketFile { + SocketFile { + flags: self.flags, + events: self.events, + read_notified: false, // we still want to notify about this new socket + write_notified: false, + read_timeout: self.read_timeout, + write_timeout: self.write_timeout, + socket_handle: self.socket_handle, + data, + } + } + + pub fn new_with_data(socket_handle: SocketHandle, data: DataT) -> SocketFile { + SocketFile { + flags: 0, + events: 0, + read_notified: false, + write_notified: false, + read_timeout: None, + write_timeout: None, + socket_handle, + data, + } + } +} + +#[derive(Copy, Clone)] +enum Setting { + Ttl, + ReadTimeout, + WriteTimeout, + #[allow(dead_code)] + Other(SettingT), +} + +pub struct SettingFile { + fd: usize, + socket_handle: SocketHandle, + setting: Setting, +} + +pub enum SchemeFile +where + SocketT: SchemeSocket, +{ + Setting(SettingFile), + Socket(SocketFile), +} + +impl SchemeFile +where + SocketT: SchemeSocket, +{ + pub fn socket_handle(&self) -> SocketHandle { + match *self { + SchemeFile::Socket(SocketFile { socket_handle, .. }) + | SchemeFile::Setting(SettingFile { socket_handle, .. }) => socket_handle, + } + } + + pub fn events(&mut self, socket_set: &mut SocketSet) -> usize + where + SocketT: AnySocket<'static>, + { + let mut revents = 0; + if let &mut SchemeFile::Socket(SocketFile { + socket_handle, + events, + ref mut read_notified, + ref mut write_notified, + .. + }) = self + { + let socket = socket_set.get::(socket_handle); + + if events & syscall::EVENT_READ.bits() == syscall::EVENT_READ.bits() + && (socket.can_recv() || !socket.may_recv()) + { + if !*read_notified { + *read_notified = true; + revents |= EVENT_READ.bits(); + } + } else { + *read_notified = false; + } + + if events & syscall::EVENT_WRITE.bits() == syscall::EVENT_WRITE.bits() + && socket.can_send() + { + if !*write_notified { + *write_notified = true; + revents |= EVENT_WRITE.bits(); + } + } else { + *write_notified = false; + } + } + revents + } +} + +#[derive(Default, Clone)] +struct WaitHandle { + until: Option, + packet: SyscallPacket, +} + +type WaitQueue = Vec; + +pub type DupResult = Option<( + SchemeFile, + Option<(SocketHandle, ::DataT)>, +)>; + +pub trait SchemeSocket +where + Self: ::std::marker::Sized, +{ + type SchemeDataT; + type DataT; + type SettingT: Copy; + + fn new_scheme_data() -> Self::SchemeDataT; + + fn can_send(&self) -> bool; + fn can_recv(&self) -> bool; + fn may_recv(&self) -> bool; + + fn hop_limit(&self) -> u8; + fn set_hop_limit(&mut self, hop_limit: u8); + + fn get_setting( + file: &SocketFile, + setting: Self::SettingT, + buf: &mut [u8], + ) -> SyscallResult; + fn set_setting( + file: &mut SocketFile, + setting: Self::SettingT, + buf: &[u8], + ) -> SyscallResult; + + fn new_socket( + sockets: &mut SocketSet, + path: &str, + uid: u32, + data: &mut Self::SchemeDataT, + context: &Context, + ) -> SyscallResult<(SocketHandle, Self::DataT)>; + + fn close_file( + &self, + file: &SchemeFile, + data: &mut Self::SchemeDataT, + ) -> SyscallResult<()>; + + fn write_buf( + &mut self, + file: &mut SocketFile, + buf: &[u8], + ) -> SyscallResult>; + + fn read_buf( + &mut self, + file: &mut SocketFile, + buf: &mut [u8], + ) -> SyscallResult>; + + fn fpath(&self, file: &SchemeFile, buf: &mut [u8]) -> SyscallResult; + + fn dup( + sockets: &mut SocketSet, + file: &mut SchemeFile, + path: &str, + data: &mut Self::SchemeDataT, + ) -> SyscallResult>; +} + +pub struct SocketScheme +where + SocketT: SchemeSocket + AnySocket<'static>, +{ + next_fd: usize, + nulls: BTreeMap, + files: BTreeMap>, + ref_counts: BTreeMap, + context: Context, + socket_set: Rc>, + scheme_file: File, + wait_queue: WaitQueue, + scheme_data: SocketT::SchemeDataT, + _phantom_socket: PhantomData, +} + +impl SocketScheme +where + SocketT: SchemeSocket + AnySocket<'static>, +{ + pub fn new( + iface: Interface, + route_table: Rc>, + socket_set: Rc>, + scheme_file: File, + ) -> SocketScheme { + SocketScheme { + next_fd: 1, + nulls: BTreeMap::new(), + files: BTreeMap::new(), + ref_counts: BTreeMap::new(), + socket_set, + scheme_data: SocketT::new_scheme_data(), + scheme_file, + wait_queue: Vec::new(), + _phantom_socket: PhantomData, + context: Context { iface, route_table }, + } + } + + pub fn on_scheme_event(&mut self) -> Result> { + let result = loop { + let mut packet = SyscallPacket::default(); + match self.scheme_file.read(&mut packet) { + Ok(0) => { + //TODO: Cleanup must occur + break Some(()); + } + Ok(_) => (), + Err(err) => { + if err.kind() == ErrorKind::WouldBlock { + break None; + } else { + return Err(Error::from(err)); + } + } + } + if packet.a == KSMSG_CANCEL { + println!("smolnetd: todo: handle cancellation"); + continue; + } + if let Some(a) = self.handle(&mut packet) { + packet.a = a; + self.scheme_file.write_all(&packet)?; + } else { + match self.handle_block(&mut packet) { + Ok(timeout) => { + self.wait_queue.push(WaitHandle { + until: timeout, + packet, + }); + } + Err(err) => { + packet.a = (-err.errno) as usize; + self.scheme_file.write_all(&packet)?; + return Err(Error::from_syscall_error( + err, + "Can't handle blocked socket", + )); + } + } + } + }; + Ok(result) + } + + pub fn notify_sockets(&mut self) -> Result<()> { + let cur_time = libredox::call::clock_gettime(flag::CLOCK_MONOTONIC) + .map_err(|e| Error::from_syscall_error(e.into(), "Can't get time"))?; + + // Notify non-blocking sockets + for (&fd, ref mut file) in &mut self.files { + let events = { + let mut socket_set = self.socket_set.borrow_mut(); + file.events(&mut socket_set) + }; + if events > 0 { + post_fevent(&mut self.scheme_file, fd, events, 1)?; + } + } + + // Wake up blocking queue + let mut i = 0; + while i < self.wait_queue.len() { + let mut packet = self.wait_queue[i].packet; + if let Some(a) = self.handle(&packet) { + self.wait_queue.remove(i); + packet.a = a; + self.scheme_file.write_all(&packet)?; + } else { + match self.wait_queue[i].until { + Some(until) + if (until.tv_sec < cur_time.tv_sec + || (until.tv_sec == cur_time.tv_sec + && i64::from(until.tv_nsec) < i64::from(cur_time.tv_nsec))) => + { + self.wait_queue.remove(i); + packet.a = (-syscall::ETIMEDOUT) as usize; + self.scheme_file.write_all(&packet)?; + } + _ => { + i += 1; + } + } + } + } + + Ok(()) + } + + fn handle_block(&mut self, packet: &mut SyscallPacket) -> SyscallResult> { + let fd = packet.b; + let (read_timeout, write_timeout) = { + let file = self + .files + .get(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + if let SchemeFile::Socket(ref scheme_file) = *file { + Ok((scheme_file.read_timeout, scheme_file.write_timeout)) + } else { + Err(SyscallError::new(syscall::EBADF)) + } + }?; + + let mut timeout = match packet.a { + syscall::SYS_WRITE => write_timeout, + syscall::SYS_READ => read_timeout, + _ => None, + }; + + if let Some(ref mut timeout) = timeout { + let cur_time = libredox::call::clock_gettime(CLOCK_MONOTONIC)?; + *timeout = add_time( + timeout, + &TimeSpec { + tv_sec: cur_time.tv_sec, + tv_nsec: cur_time.tv_nsec as i32, + }, + ) + } + + Ok(timeout) + } + + fn get_setting( + &mut self, + fd: usize, + setting: Setting, + buf: &mut [u8], + ) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + let file = match *file { + SchemeFile::Socket(ref mut file) => file, + _ => { + return Err(SyscallError::new(syscall::EBADF)); + } + }; + + match setting { + Setting::Other(setting) => SocketT::get_setting(file, setting, buf), + Setting::Ttl => { + if let Some(hop_limit) = buf.get_mut(0) { + let socket_set = self.socket_set.borrow(); + let socket = socket_set.get::(file.socket_handle); + *hop_limit = socket.hop_limit(); + Ok(1) + } else { + Err(SyscallError::new(syscall::EIO)) + } + } + Setting::ReadTimeout | Setting::WriteTimeout => { + let timespec = match (setting, file.read_timeout, file.write_timeout) { + (Setting::ReadTimeout, Some(read_timeout), _) => read_timeout, + (Setting::WriteTimeout, _, Some(write_timeout)) => write_timeout, + _ => { + return Ok(0); + } + }; + + if buf.len() < mem::size_of::() { + Ok(0) + } else { + let count = timespec.deref().read(buf).map_err(|err| { + SyscallError::new(err.raw_os_error().unwrap_or(syscall::EIO)) + })?; + Ok(count) + } + } + } + } + + fn update_setting( + &mut self, + fd: usize, + setting: Setting, + buf: &[u8], + ) -> SyscallResult { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + let file = match *file { + SchemeFile::Socket(ref mut file) => file, + _ => { + return Err(SyscallError::new(syscall::EBADF)); + } + }; + match setting { + Setting::ReadTimeout | Setting::WriteTimeout => { + let (timeout, count) = { + if buf.len() < mem::size_of::() { + (None, 0) + } else { + let mut timespec = TimeSpec::default(); + let count = timespec.deref_mut().write(buf).map_err(|err| { + SyscallError::new(err.raw_os_error().unwrap_or(syscall::EIO)) + })?; + (Some(timespec), count) + } + }; + match setting { + Setting::ReadTimeout => { + file.read_timeout = timeout; + } + Setting::WriteTimeout => { + file.write_timeout = timeout; + } + _ => {} + }; + Ok(count) + } + Setting::Ttl => { + if let Some(hop_limit) = buf.get(0) { + let mut socket_set = self.socket_set.borrow_mut(); + let socket = socket_set.get_mut::(file.socket_handle); + socket.set_hop_limit(*hop_limit); + Ok(1) + } else { + Err(SyscallError::new(syscall::EIO)) + } + } + Setting::Other(setting) => SocketT::set_setting(file, setting, buf), + } + } +} + +impl syscall::SchemeBlockMut for SocketScheme +where + SocketT: SchemeSocket + AnySocket<'static>, +{ + fn open( + &mut self, + path: &str, + flags: usize, + uid: u32, + _gid: u32, + ) -> SyscallResult> { + if path.is_empty() { + let null = NullFile { + flags, + uid, + gid: _gid, + }; + + let id = self.next_fd; + self.next_fd += 1; + + self.nulls.insert(id, null); + + Ok(Some(id)) + } else { + let (socket_handle, data) = SocketT::new_socket( + &mut self.socket_set.borrow_mut(), + path, + uid, + &mut self.scheme_data, + &self.context, + )?; + + let file = SchemeFile::Socket(SocketFile { + flags, + events: 0, + socket_handle, + read_notified: false, + write_notified: false, + write_timeout: None, + read_timeout: None, + data, + }); + + let id = self.next_fd; + self.next_fd += 1; + + self.ref_counts.insert(socket_handle, 1); + self.files.insert(id, file); + + Ok(Some(id)) + } + } + + fn close(&mut self, fd: usize) -> SyscallResult> { + if let Some(_null) = self.nulls.remove(&fd) { + return Ok(Some(0)); + } + + let socket_handle = { + let file = self + .files + .get(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + file.socket_handle() + }; + let scheme_file = self.files.remove(&fd); + let mut socket_set = self.socket_set.borrow_mut(); + if let Some(scheme_file) = scheme_file { + let socket = socket_set.get::(socket_handle); + socket.close_file(&scheme_file, &mut self.scheme_data)?; + } + + self.wait_queue.retain( + |&WaitHandle { + packet: SyscallPacket { a, .. }, + .. + }| a != fd, + ); + + let remove = match self.ref_counts.entry(socket_handle) { + Entry::Vacant(_) => { + warn!("Closing a socket_handle with no ref"); + true + } + Entry::Occupied(mut e) => { + if *e.get() == 0 { + warn!("Closing a socket_handle with no ref"); + e.remove(); + true + } else { + *e.get_mut() -= 1; + if *e.get() == 0 { + e.remove(); + true + } else { + false + } + } + } + }; + + if remove { + socket_set.remove(socket_handle); + } + Ok(Some(0)) + } + + fn write(&mut self, fd: usize, buf: &[u8]) -> SyscallResult> { + let (fd, setting) = { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + match *file { + SchemeFile::Setting(ref setting_handle) => { + (setting_handle.fd, setting_handle.setting) + } + SchemeFile::Socket(ref mut file) => { + let mut socket_set = self.socket_set.borrow_mut(); + let socket = socket_set.get_mut::(file.socket_handle); + let ret = SocketT::write_buf(socket, file, buf); + match ret { + Ok(None) => {} + _ => file.write_notified = false, + } + return ret; + } + } + }; + self.update_setting(fd, setting, buf).map(Some) + } + + fn read(&mut self, fd: usize, buf: &mut [u8]) -> SyscallResult> { + let (fd, setting) = { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + match *file { + SchemeFile::Setting(ref setting_handle) => { + (setting_handle.fd, setting_handle.setting) + } + SchemeFile::Socket(ref mut file) => { + let mut socket_set = self.socket_set.borrow_mut(); + let socket = socket_set.get_mut::(file.socket_handle); + + let ret = SocketT::read_buf(socket, file, buf); + match ret { + Ok(None) => {} + _ => file.read_notified = false, + } + + return ret; + } + } + }; + self.get_setting(fd, setting, buf).map(Some) + } + + fn dup(&mut self, fd: usize, buf: &[u8]) -> SyscallResult> { + let path = str::from_utf8(buf).or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?; + + if let Some((flags, uid, gid)) = self + .nulls + .get(&fd) + .map(|null| (null.flags, null.uid, null.gid)) + { + return self.open(path, flags, uid, gid); + } + + let new_file = { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + let socket_handle = file.socket_handle(); + + let (new_handle, update_with) = match path { + "hop_limit" => ( + SchemeFile::Setting(SettingFile { + socket_handle, + fd, + setting: Setting::Ttl, + }), + None, + ), + "read_timeout" => ( + SchemeFile::Setting(SettingFile { + socket_handle, + fd, + setting: Setting::ReadTimeout, + }), + None, + ), + "write_timeout" => ( + SchemeFile::Setting(SettingFile { + socket_handle, + fd, + setting: Setting::WriteTimeout, + }), + None, + ), + _ => match SocketT::dup( + &mut self.socket_set.borrow_mut(), + file, + path, + &mut self.scheme_data, + )? { + Some(some) => some, + None => return Ok(None), + }, + }; + + if let Some((socket_handle, data)) = update_with { + if let SchemeFile::Socket(ref mut file) = *file { + // We replace the socket_handle pointed by file so update the ref_counts + // accordingly + self.ref_counts + .entry(file.socket_handle) + .and_modify(|e| *e = e.saturating_sub(1)) + .or_insert(0); + + *self.ref_counts.entry(socket_handle).or_insert(0) += 1; + + file.socket_handle = socket_handle; + file.data = data; + } + } + *self + .ref_counts + .entry(new_handle.socket_handle()) + .or_insert(0) += 1; + new_handle + }; + + let id = self.next_fd; + self.files.insert(id, new_file); + self.next_fd += 1; + + Ok(Some(id)) + } + + fn fevent( + &mut self, + fd: usize, + events: SyscallEventFlags, + ) -> SyscallResult> { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + match *file { + SchemeFile::Setting(_) => return Err(SyscallError::new(syscall::EBADF)), + SchemeFile::Socket(ref mut file) => { + file.events = events.bits(); + file.read_notified = false; // resend missed events + file.write_notified = false; + } + } + let mut socket_set = self.socket_set.borrow_mut(); + let revents = SyscallEventFlags::from_bits_truncate(file.events(&mut socket_set)); + Ok(Some(revents)) + } + + fn fsync(&mut self, fd: usize) -> SyscallResult> { + { + let _file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + } + Ok(Some(0)) + // TODO Implement fsyncing + // self.0.network_fsync() + } + + fn fpath(&mut self, fd: usize, buf: &mut [u8]) -> SyscallResult> { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + let socket_set = self.socket_set.borrow(); + let socket = socket_set.get::(file.socket_handle()); + + socket.fpath(file, buf).map(Some) + } + + fn fcntl(&mut self, fd: usize, cmd: usize, arg: usize) -> SyscallResult> { + if let Some(ref mut null) = self.nulls.get_mut(&fd) { + match cmd { + syscall::F_GETFL => Ok(Some(null.flags)), + syscall::F_SETFL => { + null.flags = arg & !syscall::O_ACCMODE; + Ok(Some(0)) + } + _ => Err(SyscallError::new(syscall::EINVAL)), + } + } else { + let file = self + .files + .get_mut(&fd) + .ok_or_else(|| SyscallError::new(syscall::EBADF))?; + + if let SchemeFile::Socket(ref mut socket_file) = *file { + match cmd { + syscall::F_GETFL => Ok(Some(socket_file.flags)), + syscall::F_SETFL => { + socket_file.flags = arg & !syscall::O_ACCMODE; + Ok(Some(0)) + } + _ => Err(SyscallError::new(syscall::EINVAL)), + } + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } + } +} + +fn add_time(a: &TimeSpec, b: &TimeSpec) -> TimeSpec { + let mut secs = a.tv_sec + b.tv_sec; + let mut nsecs = a.tv_nsec + b.tv_nsec; + + secs += i64::from(nsecs) / 1_000_000_000; + nsecs %= 1_000_000_000; + + TimeSpec { + tv_sec: secs, + tv_nsec: nsecs, + } +} diff --git a/netstack/src/smolnetd/scheme/tcp.rs b/netstack/src/smolnetd/scheme/tcp.rs new file mode 100644 index 0000000000..3dcd13425f --- /dev/null +++ b/netstack/src/smolnetd/scheme/tcp.rs @@ -0,0 +1,301 @@ +use smoltcp::iface::SocketHandle; +use smoltcp::socket::tcp::{Socket as TcpSocket, SocketBuffer as TcpSocketBuffer}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; +use std::fmt::Write; +use std::str; +use syscall; +use syscall::{Error as SyscallError, Result as SyscallResult}; + +use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile, SocketScheme}; +use super::{parse_endpoint, SocketSet}; +use crate::port_set::PortSet; + +pub type TcpScheme = SocketScheme>; + +impl<'a> SchemeSocket for TcpSocket<'a> { + type SchemeDataT = PortSet; + type DataT = Option; + type SettingT = (); + + fn new_scheme_data() -> Self::SchemeDataT { + PortSet::new(49_152u16, 65_535u16).expect("Wrong TCP port numbers") + } + + fn can_send(&self) -> bool { + self.can_send() + } + + fn can_recv(&self) -> bool { + self.can_recv() + } + + fn may_recv(&self) -> bool { + self.may_recv() + } + + fn hop_limit(&self) -> u8 { + self.hop_limit().unwrap_or(64) + } + + fn set_hop_limit(&mut self, hop_limit: u8) { + self.set_hop_limit(Some(hop_limit)); + } + + fn get_setting( + _file: &SocketFile, + _setting: Self::SettingT, + _buf: &mut [u8], + ) -> SyscallResult { + Ok(0) + } + + fn set_setting( + _file: &mut SocketFile, + _setting: Self::SettingT, + _buf: &[u8], + ) -> SyscallResult { + Ok(0) + } + + fn new_socket( + socket_set: &mut SocketSet, + path: &str, + uid: u32, + port_set: &mut Self::SchemeDataT, + context: &Context, + ) -> SyscallResult<(SocketHandle, Self::DataT)> { + trace!("TCP open {}", path); + let mut parts = path.split('/'); + let remote_endpoint = parse_endpoint(parts.next().unwrap_or("")); + let mut local_endpoint = parse_endpoint(parts.next().unwrap_or("")); + + if local_endpoint.port > 0 && local_endpoint.port <= 1024 && uid != 0 { + return Err(SyscallError::new(syscall::EACCES)); + } + + let rx_packets = vec![0; 0xffff]; + let tx_packets = vec![0; 0xffff]; + let rx_buffer = TcpSocketBuffer::new(rx_packets); + let tx_buffer = TcpSocketBuffer::new(tx_packets); + let socket = TcpSocket::new(rx_buffer, tx_buffer); + + if local_endpoint.port == 0 { + local_endpoint.port = port_set + .get_port() + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; + } else if !port_set.claim_port(local_endpoint.port) { + return Err(SyscallError::new(syscall::EADDRINUSE)); + } + + let socket_handle = socket_set.add(socket); + + let tcp_socket = socket_set.get_mut::(socket_handle); + + let listen_enpoint = if remote_endpoint.is_specified() { + let local_endpoint_addr = match local_endpoint.addr { + Some(addr) if !addr.is_unspecified() => Some(addr), + _ => { + let route_table = context.route_table.borrow(); + let addr = route_table + .lookup_src_addr(&remote_endpoint.addr.expect("Checked in is_specified")); + if matches!(addr, None) { + error!("Opening a TCP connection with a probably invalid source IP as no route have been found for destination: {}", remote_endpoint); + } + addr + } + }; + let local_endpoint = IpListenEndpoint { + addr: local_endpoint_addr, + port: local_endpoint.port, + }; + + trace!("Connecting tcp {} {}", local_endpoint, remote_endpoint); + tcp_socket + .connect( + context.iface.borrow_mut().context(), + IpEndpoint::new(remote_endpoint.addr.unwrap(), remote_endpoint.port), + local_endpoint, + ) + .expect("Can't connect tcp socket "); + None + } else { + trace!("Listening tcp {}", local_endpoint); + tcp_socket + .listen(local_endpoint) + .expect("Can't listen on local endpoint"); + Some(local_endpoint) + }; + + Ok((socket_handle, listen_enpoint)) + } + + fn close_file( + &self, + file: &SchemeFile, + port_set: &mut Self::SchemeDataT, + ) -> SyscallResult<()> { + if let SchemeFile::Socket(SocketFile { data, .. }) = *file { + if let Some(endpoint) = self.local_endpoint() { + // Socket was connected on some port + port_set.release_port(endpoint.port); + } else if let Some(endpoint) = data { + // Socket was listening on some port + port_set.release_port(endpoint.port); + } + } + Ok(()) + } + + fn write_buf( + &mut self, + file: &mut SocketFile, + buf: &[u8], + ) -> SyscallResult> { + if !self.is_active() { + Err(SyscallError::new(syscall::ENOTCONN)) + } else if self.can_send() { + self.send_slice(buf).expect("Can't send slice"); + Ok(Some(buf.len())) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-write + } + } + + fn read_buf( + &mut self, + file: &mut SocketFile, + buf: &mut [u8], + ) -> SyscallResult> { + if !self.is_active() { + Err(SyscallError::new(syscall::ENOTCONN)) + } else if self.can_recv() { + let length = self.recv_slice(buf).expect("Can't receive slice"); + Ok(Some(length)) + } else if !self.may_recv() { + Ok(Some(0)) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn dup( + socket_set: &mut SocketSet, + file: &mut SchemeFile, + path: &str, + port_set: &mut Self::SchemeDataT, + ) -> SyscallResult> { + let socket_handle = file.socket_handle(); + + let (is_active, local_endpoint) = { + let socket = socket_set.get::(socket_handle); + (socket.is_active(), socket.local_endpoint()) + }; + + let file = match path { + "listen" => { + if let SchemeFile::Socket(ref tcp_handle) = *file { + let Some(listen_enpoint) = tcp_handle.data else { + // This socket is not listening so we can't accept a connection + return Err(SyscallError::new(syscall::EINVAL)); + }; + + if !is_active { + // Socket listening but no connection received + if tcp_handle.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + return Err(SyscallError::new(syscall::EAGAIN)); + } else { + return Ok(None); + } + } + trace!("TCP creating new listening socket"); + // We pass None as data because this new handle is to the active connection so + // not a listening socket + let new_handle = SchemeFile::Socket(tcp_handle.clone_with_data(None)); + + // Creating a socket to continue listening + let rx_packets = vec![0; 0xffff]; + let tx_packets = vec![0; 0xffff]; + let rx_buffer = TcpSocketBuffer::new(rx_packets); + let tx_buffer = TcpSocketBuffer::new(tx_packets); + let socket = TcpSocket::new(rx_buffer, tx_buffer); + let new_socket_handle = socket_set.add(socket); + { + let tcp_socket = socket_set.get_mut::(new_socket_handle); + tcp_socket + .listen(listen_enpoint) + .expect("Can't listen on local endpoint"); + } + // We got a new connection to the socket so acquire the port + port_set.acquire_port( + local_endpoint + .expect("Socket was active so local endpoint must be set") + .port, + ); + return Ok(Some(( + new_handle, + Some((new_socket_handle, Some(listen_enpoint))), + ))); + } else { + return Err(SyscallError::new(syscall::EBADF)); + } + } + _ => { + trace!("TCP dup unknown {}", path); + if let SchemeFile::Socket(ref tcp_handle) = *file { + SchemeFile::Socket(tcp_handle.clone_with_data(tcp_handle.data)) + } else { + SchemeFile::Socket(SocketFile::new_with_data(socket_handle, None)) + } + } + }; + + if let SchemeFile::Socket(_) = file { + if let Some(local_endpoint) = local_endpoint { + port_set.acquire_port(local_endpoint.port); + } + } + + Ok(Some((file, None))) + } + + fn fpath(&self, file: &SchemeFile, buf: &mut [u8]) -> SyscallResult { + let unspecified = "0.0.0.0:0"; + let mut path = String::from("tcp:"); + match self.remote_endpoint() { + Some(endpoint) => write!(&mut path, "{}", endpoint).unwrap(), + None => path.push_str(unspecified), + } + path.push('/'); + match (self.local_endpoint(), file) { + (Some(endpoint), _) => write!(&mut path, "{}", endpoint).unwrap(), + ( + None, + SchemeFile::Socket(SocketFile { + data: Some(endpoint), + .. + }), + ) => { + if endpoint.is_specified() { + write!(&mut path, "{}", endpoint).unwrap() + } else { + write!(&mut path, "0.0.0.0:{}", endpoint.port).unwrap() + } + } + _ => path.push_str(unspecified), + } + trace!("fpath: {}", path); + let path = path.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } +} diff --git a/netstack/src/smolnetd/scheme/udp.rs b/netstack/src/smolnetd/scheme/udp.rs new file mode 100644 index 0000000000..e02a513d0c --- /dev/null +++ b/netstack/src/smolnetd/scheme/udp.rs @@ -0,0 +1,208 @@ +use smoltcp::iface::SocketHandle; +use smoltcp::socket::udp::{ + PacketBuffer as UdpSocketBuffer, PacketMetadata as UdpPacketMetadata, Socket as UdpSocket, +}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; +use std::str; +use syscall; +use syscall::{Error as SyscallError, Result as SyscallResult}; + +use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile, SocketScheme}; +use super::{parse_endpoint, Smolnetd, SocketSet}; +use crate::port_set::PortSet; +use crate::router::Router; + +pub type UdpScheme = SocketScheme>; + +impl<'a> SchemeSocket for UdpSocket<'a> { + type SchemeDataT = PortSet; + type DataT = IpListenEndpoint; + type SettingT = (); + + fn new_scheme_data() -> Self::SchemeDataT { + PortSet::new(49_152u16, 65_535u16).expect("Wrong UDP port numbers") + } + + fn can_send(&self) -> bool { + self.can_send() + } + + fn can_recv(&self) -> bool { + self.can_recv() + } + + fn may_recv(&self) -> bool { + true + } + + fn hop_limit(&self) -> u8 { + self.hop_limit().unwrap_or(64) + } + + fn set_hop_limit(&mut self, hop_limit: u8) { + self.set_hop_limit(Some(hop_limit)); + } + + fn get_setting( + _file: &SocketFile, + _setting: Self::SettingT, + _buf: &mut [u8], + ) -> SyscallResult { + Ok(0) + } + + fn set_setting( + _file: &mut SocketFile, + _setting: Self::SettingT, + _buf: &[u8], + ) -> SyscallResult { + Ok(0) + } + + fn new_socket( + socket_set: &mut SocketSet, + path: &str, + uid: u32, + port_set: &mut Self::SchemeDataT, + _context: &Context, + ) -> SyscallResult<(SocketHandle, Self::DataT)> { + let mut parts = path.split('/'); + let remote_endpoint = parse_endpoint(parts.next().unwrap_or("")); + let mut local_endpoint = parse_endpoint(parts.next().unwrap_or("")); + + if local_endpoint.port > 0 && local_endpoint.port <= 1024 && uid != 0 { + return Err(SyscallError::new(syscall::EACCES)); + } + + let rx_buffer = UdpSocketBuffer::new( + vec![UdpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + let tx_buffer = UdpSocketBuffer::new( + vec![UdpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], + vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE], + ); + let udp_socket = UdpSocket::new(rx_buffer, tx_buffer); + + if local_endpoint.port == 0 { + local_endpoint.port = port_set + .get_port() + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; + } else if !port_set.claim_port(local_endpoint.port) { + return Err(SyscallError::new(syscall::EADDRINUSE)); + } + + let socket_handle = socket_set.add(udp_socket); + + let udp_socket = socket_set.get_mut::(socket_handle); + udp_socket + .bind(local_endpoint) + .expect("Can't bind udp socket to local endpoint"); + + Ok((socket_handle, remote_endpoint)) + } + + fn close_file( + &self, + file: &SchemeFile, + port_set: &mut Self::SchemeDataT, + ) -> SyscallResult<()> { + if let SchemeFile::Socket(_) = *file { + port_set.release_port(self.endpoint().port); + } + Ok(()) + } + + fn write_buf( + &mut self, + file: &mut SocketFile, + buf: &[u8], + ) -> SyscallResult> { + if !file.data.is_specified() { + return Err(SyscallError::new(syscall::EADDRNOTAVAIL)); + } + if self.can_send() { + let endpoint = file.data; + let endpoint = IpEndpoint::new( + endpoint + .addr + .expect("If we can send, this should be specified"), + endpoint.port, + ); + self.send_slice(buf, endpoint).expect("Can't send slice"); + Ok(Some(buf.len())) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn read_buf( + &mut self, + file: &mut SocketFile, + buf: &mut [u8], + ) -> SyscallResult> { + if self.can_recv() { + let (length, _) = self.recv_slice(buf).expect("Can't receive slice"); + Ok(Some(length)) + } else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK { + Err(SyscallError::new(syscall::EAGAIN)) + } else { + Ok(None) // internally scheduled to re-read + } + } + + fn dup( + socket_set: &mut SocketSet, + file: &mut SchemeFile, + path: &str, + port_set: &mut Self::SchemeDataT, + ) -> SyscallResult> { + let socket_handle = file.socket_handle(); + let file = match path { + _ => { + let remote_endpoint = parse_endpoint(path); + if let SchemeFile::Socket(ref udp_handle) = *file { + SchemeFile::Socket(udp_handle.clone_with_data( + if remote_endpoint.is_specified() { + remote_endpoint + } else { + udp_handle.data + }, + )) + } else { + SchemeFile::Socket(SocketFile::new_with_data(socket_handle, remote_endpoint)) + } + } + }; + + let endpoint = { + let socket = socket_set.get::(socket_handle); + socket.endpoint() + }; + + if let SchemeFile::Socket(_) = file { + port_set.acquire_port(endpoint.port); + } + + Ok(Some((file, None))) + } + + fn fpath(&self, file: &SchemeFile, buf: &mut [u8]) -> SyscallResult { + if let SchemeFile::Socket(ref socket_file) = *file { + let path = format!("udp:{}/{}", socket_file.data, self.endpoint()); + let path = path.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } else { + Err(SyscallError::new(syscall::EBADF)) + } + } +}