├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── Readme.md ├── examples ├── async_tun.rs ├── async_tun_utils.rs ├── sync_tun.rs └── utils.rs └── src ├── async_smoltcp.rs ├── lib.rs ├── packet_log.rs └── virtual_tun.rs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | WORKDIR /home/project 4 | 5 | RUN export DEBIAN_FRONTEND=noninteractive && apt-get update\ 6 | && apt-get install -y build-essential curl autoconf git m4 \ 7 | iproute2 clang libpq-dev clang cmake make liblz4-dev libssl-dev \ 8 | liblzo2-dev libasio-dev 9 | ENV PATH="/root/.cargo/bin:/usr/local/cmake/bin:${PATH}" 10 | 11 | RUN curl --proto '=https' --tlsv1.2 -o rust.sh https://sh.rustup.rs\ 12 | && /bin/bash rust.sh -y 13 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rust environment", 3 | "dockerFile": "Dockerfile", 4 | "extensions": [ 5 | "matklad.rust-analyzer" 6 | ], 7 | "containerEnv": { 8 | "VPN_PROFILES_DIR": "openvpn_profiles", 9 | } 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/* 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "async_smoltcp"] 2 | path = async_smoltcp 3 | url = https://github.com/lattice0/async_smoltcp 4 | [submodule "true_libopenvpn3_rust"] 5 | path = true_libopenvpn3_rust 6 | url = https://github.com/lattice0/true_libopenvpn3_rust 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "async_smoltcp" 16 | version = "0.1.0" 17 | dependencies = [ 18 | "crossbeam-channel", 19 | "env_logger", 20 | "etherparse", 21 | "futures", 22 | "getopts", 23 | "hyper", 24 | "log", 25 | "rand", 26 | "simple_socket", 27 | "simple_vpn", 28 | "smoltcp", 29 | "tokio", 30 | ] 31 | 32 | [[package]] 33 | name = "atty" 34 | version = "0.2.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 37 | dependencies = [ 38 | "hermit-abi", 39 | "libc", 40 | "winapi", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 48 | 49 | [[package]] 50 | name = "bitflags" 51 | version = "1.2.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 54 | 55 | [[package]] 56 | name = "byteorder" 57 | version = "1.4.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 60 | 61 | [[package]] 62 | name = "bytes" 63 | version = "1.0.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "crossbeam-channel" 75 | version = "0.5.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 78 | dependencies = [ 79 | "cfg-if", 80 | "crossbeam-utils", 81 | ] 82 | 83 | [[package]] 84 | name = "crossbeam-utils" 85 | version = "0.8.5" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 88 | dependencies = [ 89 | "cfg-if", 90 | "lazy_static", 91 | ] 92 | 93 | [[package]] 94 | name = "env_logger" 95 | version = "0.8.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 98 | dependencies = [ 99 | "atty", 100 | "humantime", 101 | "log", 102 | "regex", 103 | "termcolor", 104 | ] 105 | 106 | [[package]] 107 | name = "etherparse" 108 | version = "0.9.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "2fa20922281f9ee5ffcda45e80d56085829279f1270f79fbabc39809a4354807" 111 | dependencies = [ 112 | "byteorder", 113 | ] 114 | 115 | [[package]] 116 | name = "fnv" 117 | version = "1.0.7" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 120 | 121 | [[package]] 122 | name = "futures" 123 | version = "0.3.15" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" 126 | dependencies = [ 127 | "futures-channel", 128 | "futures-core", 129 | "futures-executor", 130 | "futures-io", 131 | "futures-sink", 132 | "futures-task", 133 | "futures-util", 134 | ] 135 | 136 | [[package]] 137 | name = "futures-channel" 138 | version = "0.3.15" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" 141 | dependencies = [ 142 | "futures-core", 143 | "futures-sink", 144 | ] 145 | 146 | [[package]] 147 | name = "futures-core" 148 | version = "0.3.15" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" 151 | 152 | [[package]] 153 | name = "futures-executor" 154 | version = "0.3.15" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" 157 | dependencies = [ 158 | "futures-core", 159 | "futures-task", 160 | "futures-util", 161 | ] 162 | 163 | [[package]] 164 | name = "futures-io" 165 | version = "0.3.15" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" 168 | 169 | [[package]] 170 | name = "futures-sink" 171 | version = "0.3.15" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" 174 | 175 | [[package]] 176 | name = "futures-task" 177 | version = "0.3.15" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" 180 | 181 | [[package]] 182 | name = "futures-util" 183 | version = "0.3.15" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" 186 | dependencies = [ 187 | "autocfg", 188 | "futures-channel", 189 | "futures-core", 190 | "futures-io", 191 | "futures-sink", 192 | "futures-task", 193 | "memchr", 194 | "pin-project-lite", 195 | "pin-utils", 196 | "slab", 197 | ] 198 | 199 | [[package]] 200 | name = "getopts" 201 | version = "0.2.21" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 204 | dependencies = [ 205 | "unicode-width", 206 | ] 207 | 208 | [[package]] 209 | name = "getrandom" 210 | version = "0.2.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 213 | dependencies = [ 214 | "cfg-if", 215 | "libc", 216 | "wasi", 217 | ] 218 | 219 | [[package]] 220 | name = "h2" 221 | version = "0.3.3" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" 224 | dependencies = [ 225 | "bytes", 226 | "fnv", 227 | "futures-core", 228 | "futures-sink", 229 | "futures-util", 230 | "http", 231 | "indexmap", 232 | "slab", 233 | "tokio", 234 | "tokio-util", 235 | "tracing", 236 | ] 237 | 238 | [[package]] 239 | name = "hashbrown" 240 | version = "0.9.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 243 | 244 | [[package]] 245 | name = "hermit-abi" 246 | version = "0.1.18" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 249 | dependencies = [ 250 | "libc", 251 | ] 252 | 253 | [[package]] 254 | name = "http" 255 | version = "0.2.4" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" 258 | dependencies = [ 259 | "bytes", 260 | "fnv", 261 | "itoa", 262 | ] 263 | 264 | [[package]] 265 | name = "http-body" 266 | version = "0.4.2" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" 269 | dependencies = [ 270 | "bytes", 271 | "http", 272 | "pin-project-lite", 273 | ] 274 | 275 | [[package]] 276 | name = "httparse" 277 | version = "1.4.1" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" 280 | 281 | [[package]] 282 | name = "httpdate" 283 | version = "1.0.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" 286 | 287 | [[package]] 288 | name = "humantime" 289 | version = "2.1.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 292 | 293 | [[package]] 294 | name = "hyper" 295 | version = "0.14.9" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" 298 | dependencies = [ 299 | "bytes", 300 | "futures-channel", 301 | "futures-core", 302 | "futures-util", 303 | "h2", 304 | "http", 305 | "http-body", 306 | "httparse", 307 | "httpdate", 308 | "itoa", 309 | "pin-project-lite", 310 | "socket2", 311 | "tokio", 312 | "tower-service", 313 | "tracing", 314 | "want", 315 | ] 316 | 317 | [[package]] 318 | name = "indexmap" 319 | version = "1.6.2" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" 322 | dependencies = [ 323 | "autocfg", 324 | "hashbrown", 325 | ] 326 | 327 | [[package]] 328 | name = "itoa" 329 | version = "0.4.7" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 332 | 333 | [[package]] 334 | name = "lazy_static" 335 | version = "1.4.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 338 | 339 | [[package]] 340 | name = "libc" 341 | version = "0.2.94" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" 344 | 345 | [[package]] 346 | name = "log" 347 | version = "0.4.14" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 350 | dependencies = [ 351 | "cfg-if", 352 | ] 353 | 354 | [[package]] 355 | name = "managed" 356 | version = "0.8.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" 359 | 360 | [[package]] 361 | name = "memchr" 362 | version = "2.4.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 365 | 366 | [[package]] 367 | name = "mio" 368 | version = "0.7.11" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 371 | dependencies = [ 372 | "libc", 373 | "log", 374 | "miow", 375 | "ntapi", 376 | "winapi", 377 | ] 378 | 379 | [[package]] 380 | name = "miow" 381 | version = "0.3.7" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 384 | dependencies = [ 385 | "winapi", 386 | ] 387 | 388 | [[package]] 389 | name = "ntapi" 390 | version = "0.3.6" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 393 | dependencies = [ 394 | "winapi", 395 | ] 396 | 397 | [[package]] 398 | name = "num_cpus" 399 | version = "1.13.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 402 | dependencies = [ 403 | "hermit-abi", 404 | "libc", 405 | ] 406 | 407 | [[package]] 408 | name = "pin-project-lite" 409 | version = "0.2.6" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 412 | 413 | [[package]] 414 | name = "pin-utils" 415 | version = "0.1.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 418 | 419 | [[package]] 420 | name = "ppv-lite86" 421 | version = "0.2.10" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 424 | 425 | [[package]] 426 | name = "proc-macro2" 427 | version = "1.0.27" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 430 | dependencies = [ 431 | "unicode-xid", 432 | ] 433 | 434 | [[package]] 435 | name = "quote" 436 | version = "1.0.9" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 439 | dependencies = [ 440 | "proc-macro2", 441 | ] 442 | 443 | [[package]] 444 | name = "rand" 445 | version = "0.8.4" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 448 | dependencies = [ 449 | "libc", 450 | "rand_chacha", 451 | "rand_core", 452 | "rand_hc", 453 | ] 454 | 455 | [[package]] 456 | name = "rand_chacha" 457 | version = "0.3.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 460 | dependencies = [ 461 | "ppv-lite86", 462 | "rand_core", 463 | ] 464 | 465 | [[package]] 466 | name = "rand_core" 467 | version = "0.6.3" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 470 | dependencies = [ 471 | "getrandom", 472 | ] 473 | 474 | [[package]] 475 | name = "rand_hc" 476 | version = "0.3.1" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 479 | dependencies = [ 480 | "rand_core", 481 | ] 482 | 483 | [[package]] 484 | name = "regex" 485 | version = "1.5.4" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 488 | dependencies = [ 489 | "aho-corasick", 490 | "memchr", 491 | "regex-syntax", 492 | ] 493 | 494 | [[package]] 495 | name = "regex-syntax" 496 | version = "0.6.25" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 499 | 500 | [[package]] 501 | name = "simple_socket" 502 | version = "0.1.0" 503 | source = "git+https://github.com/lucaszanella/simple_socket#0de9c0439d180c4c07280c336b39c5729d14f6ba" 504 | dependencies = [ 505 | "futures", 506 | ] 507 | 508 | [[package]] 509 | name = "simple_vpn" 510 | version = "0.1.0" 511 | source = "git+https://github.com/lucaszanella/simple_vpn#476990f2ba3a3ffa45eb5026a8a5fbbef13992a5" 512 | 513 | [[package]] 514 | name = "slab" 515 | version = "0.4.3" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 518 | 519 | [[package]] 520 | name = "smoltcp" 521 | version = "0.8.0" 522 | source = "git+https://github.com/smoltcp-rs/smoltcp#027f255f904b9b7c4226cfd8b2d31f272ffa5105" 523 | dependencies = [ 524 | "bitflags", 525 | "byteorder", 526 | "libc", 527 | "log", 528 | "managed", 529 | ] 530 | 531 | [[package]] 532 | name = "socket2" 533 | version = "0.4.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" 536 | dependencies = [ 537 | "libc", 538 | "winapi", 539 | ] 540 | 541 | [[package]] 542 | name = "syn" 543 | version = "1.0.72" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" 546 | dependencies = [ 547 | "proc-macro2", 548 | "quote", 549 | "unicode-xid", 550 | ] 551 | 552 | [[package]] 553 | name = "termcolor" 554 | version = "1.1.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 557 | dependencies = [ 558 | "winapi-util", 559 | ] 560 | 561 | [[package]] 562 | name = "tokio" 563 | version = "1.6.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "bd3076b5c8cc18138b8f8814895c11eb4de37114a5d127bafdc5e55798ceef37" 566 | dependencies = [ 567 | "autocfg", 568 | "bytes", 569 | "libc", 570 | "memchr", 571 | "mio", 572 | "num_cpus", 573 | "pin-project-lite", 574 | "tokio-macros", 575 | ] 576 | 577 | [[package]] 578 | name = "tokio-macros" 579 | version = "1.2.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" 582 | dependencies = [ 583 | "proc-macro2", 584 | "quote", 585 | "syn", 586 | ] 587 | 588 | [[package]] 589 | name = "tokio-util" 590 | version = "0.6.7" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" 593 | dependencies = [ 594 | "bytes", 595 | "futures-core", 596 | "futures-sink", 597 | "log", 598 | "pin-project-lite", 599 | "tokio", 600 | ] 601 | 602 | [[package]] 603 | name = "tower-service" 604 | version = "0.3.1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 607 | 608 | [[package]] 609 | name = "tracing" 610 | version = "0.1.26" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" 613 | dependencies = [ 614 | "cfg-if", 615 | "pin-project-lite", 616 | "tracing-core", 617 | ] 618 | 619 | [[package]] 620 | name = "tracing-core" 621 | version = "0.1.18" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" 624 | dependencies = [ 625 | "lazy_static", 626 | ] 627 | 628 | [[package]] 629 | name = "try-lock" 630 | version = "0.2.3" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 633 | 634 | [[package]] 635 | name = "unicode-width" 636 | version = "0.1.8" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 639 | 640 | [[package]] 641 | name = "unicode-xid" 642 | version = "0.2.2" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 645 | 646 | [[package]] 647 | name = "want" 648 | version = "0.3.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 651 | dependencies = [ 652 | "log", 653 | "try-lock", 654 | ] 655 | 656 | [[package]] 657 | name = "wasi" 658 | version = "0.10.2+wasi-snapshot-preview1" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 661 | 662 | [[package]] 663 | name = "winapi" 664 | version = "0.3.9" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 667 | dependencies = [ 668 | "winapi-i686-pc-windows-gnu", 669 | "winapi-x86_64-pc-windows-gnu", 670 | ] 671 | 672 | [[package]] 673 | name = "winapi-i686-pc-windows-gnu" 674 | version = "0.4.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 677 | 678 | [[package]] 679 | name = "winapi-util" 680 | version = "0.1.5" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 683 | dependencies = [ 684 | "winapi", 685 | ] 686 | 687 | [[package]] 688 | name = "winapi-x86_64-pc-windows-gnu" 689 | version = "0.4.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 692 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async_smoltcp" 3 | version = "0.1.0" 4 | authors = ["Lattice0"] 5 | edition = "2021" 6 | 7 | [features] 8 | default = ["tap", "tun", "async", "tcp", "udp"] 9 | vpn = ["smoltcp/medium-ip", "smoltcp/medium-ethernet", "simple_socket", "simple_vpn"] 10 | tap = ["smoltcp/medium-ethernet", "smoltcp/phy-tuntap_interface"] 11 | tun = ["smoltcp/medium-ip", "smoltcp/phy-tuntap_interface", "simple_socket"] 12 | tcp = ["smoltcp/socket-tcp", "simple_socket"] 13 | udp = ["smoltcp/socket-udp", "simple_socket"] 14 | packet_log = ["etherparse", "log"] 15 | 16 | async = ["tokio"] 17 | 18 | [dependencies] 19 | #simple_socket = {path="../simple_socket", optional=true} 20 | simple_socket = {git = "https://github.com/lattice0/simple_socket", optional=true} 21 | #Not needed in general, it's just for a personal project that adds VPN support 22 | simple_vpn = {git = "https://github.com/lattice0/simple_vpn", optional=true} 23 | #TODO: fix a release version 24 | smoltcp = {git = "https://github.com/smoltcp-rs/smoltcp", features=["std"], rev = "007f8dd2e2d0a770609428aa4b0efb63f5dbb708"} 25 | rand = "0.8.4" 26 | #TODO: can I make it depend on net only? 27 | tokio = { version = "1.4.0", features = ["rt-multi-thread", "macros", "net", "io-util", "rt", "time"], optional=true } 28 | futures = { version="0.3.13", default-features = false, features = ["std", "executor"] } 29 | log = { version="0.4.14", default-features = false, optional=true } 30 | crossbeam-channel = "0.5.1" 31 | etherparse = {version="0.9.0", optional=true} 32 | 33 | [dev-dependencies] 34 | getopts = "0.2" 35 | hyper = {version = "0.14", features = ["full"]} 36 | env_logger = "0.8.3" 37 | 38 | [[example]] 39 | name="sync_tun" 40 | required-features=["default", "log"] 41 | 42 | [[example]] 43 | name="async_tun" 44 | required-features=["default", "log"] 45 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Async smoltcp 2 | 3 | In development. Implements tokio's `AsyncWrite` and `AsyndRead` for smoltcp, and wraps everything in a struct with support for tun, tap and virtual tun. 4 | 5 | Currently you'll possibly want to use like this 6 | 7 | `async_smoltcp = {git = "https://github.com/lattice0/async_smoltcp", features=["default", "log"]}` 8 | 9 | # TODO: 10 | 11 | - bump smoltcp version 12 | -------------------------------------------------------------------------------- /examples/async_tun.rs: -------------------------------------------------------------------------------- 1 | /* 2 | To run this example, install below: 3 | 4 | apt-get install -y iproute2 iptables dnsutils 5 | 6 | then do this with sudo if outside of docker: 7 | 8 | ip tuntap add dev tun1 mode tun user `id -un` 9 | ip link set dev tun1 up 10 | ip addr add dev tun1 local 192.168.1.0 remote 192.168.1.1 11 | iptables -t filter -I FORWARD -i tun1 -o eth0 -j ACCEPT 12 | iptables -t filter -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 13 | iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE 14 | sysctl net.ipv4.ip_forward=1 15 | 16 | then 17 | 18 | cargo run --example async_tun --features="default log" -- http://neversll.com 80 19 | 20 | More information about the ip commands: https://serverfault.com/a/1067366/554980 21 | */ 22 | use core::task::{Context, Poll, Waker}; 23 | use futures::lock::Mutex as FutMutex; 24 | use hyper::body::HttpBody; 25 | use hyper::{Client, Uri}; 26 | #[allow(dead_code)] 27 | use log::{debug, error, info, warn}; 28 | use std::collections::VecDeque; 29 | use std::env; 30 | use std::fs::File; 31 | use std::io::prelude::*; 32 | use std::net::{SocketAddr, ToSocketAddrs}; 33 | use std::sync::atomic::{AtomicBool, Ordering}; 34 | use std::sync::{Arc, Mutex}; 35 | use tokio::io::{self, AsyncWriteExt as _}; 36 | 37 | mod async_tun_utils; 38 | use async_smoltcp::{IpAddress, IpCidr, IpEndpoint, SmolSocket, SmolStackWithDevice}; 39 | use async_tun_utils::AsyncTransporter; 40 | 41 | #[tokio::main] 42 | async fn main() -> Result<(), ()> { 43 | println!("Hyper async tun cli 1.0"); 44 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 45 | 46 | let stack_addr_ipv4 = std::net::Ipv4Addr::new(192, 168, 1, 1); 47 | let stack_addr_ipv6 = std::net::Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1); 48 | 49 | let url = match env::args().nth(1) { 50 | Some(url) => url, 51 | None => { 52 | info!("Usage: client "); 53 | return Ok(()); 54 | } 55 | }; 56 | 57 | let ip_addrs = vec![ 58 | IpCidr::new(IpAddress::from(stack_addr_ipv4), 0), 59 | IpCidr::new(IpAddress::from(stack_addr_ipv6), 0), 60 | ]; 61 | 62 | let stack = Arc::new(FutMutex::new(SmolStackWithDevice::new_tun( 63 | "tun1", 64 | None, 65 | None, 66 | Some(ip_addrs), 67 | ))); 68 | 69 | let socket = Arc::new(Mutex::new(stack.lock().await.add_tcp_socket().unwrap())); 70 | 71 | let uri = url.parse::().unwrap(); 72 | let domain_port = format!("{}:{}", uri.host().unwrap(), uri.port_u16().unwrap_or(80)); 73 | let ip_address = domain_port.to_socket_addrs().unwrap().next().unwrap(); 74 | let src_port: u16 = async_smoltcp::random_source_port(); 75 | 76 | stack.lock().await 77 | .tcp_connect(&socket.lock().unwrap().handle, ip_address, src_port) 78 | .unwrap(); 79 | 80 | SmolStackWithDevice::spawn_stack_thread(stack.clone()); 81 | 82 | let connector = AsyncTransporter::new(socket.clone()); 83 | let client: Client = Client::builder().build(connector.clone()); 84 | 85 | info!("getting {} at ip address {}", url, ip_address); 86 | let res = client.get(url.parse::().unwrap()).await.unwrap(); 87 | info!("Response: {}", res.status()); 88 | info!("Headers: {:#?}\n", res.headers()); 89 | //No body printing since it would be big 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /examples/async_tun_utils.rs: -------------------------------------------------------------------------------- 1 | use core::task::{Context, Poll}; 2 | use core::future::Future; 3 | use std::pin::Pin; 4 | use std::sync::Arc; 5 | use std::sync::Mutex; 6 | use tokio::io::{AsyncRead, AsyncWrite}; 7 | use hyper::service::Service; 8 | use hyper::client::connect::{Connection, Connected}; 9 | use async_smoltcp::AsyncRW; 10 | 11 | #[derive(Clone)] 12 | pub struct AsyncTransporter { 13 | stream: Arc>, 14 | } 15 | 16 | impl Service for AsyncTransporter { 17 | type Response = CustomResponse; 18 | type Error = hyper::http::Error; 19 | type Future = Pin> + Send>>; 20 | 21 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 22 | Poll::Ready(Ok(())) 23 | } 24 | 25 | fn call(&mut self, req: hyper::Uri) -> Self::Future { 26 | let resp = CustomResponse{ 27 | stream: self.stream.clone() 28 | }; 29 | 30 | let fut = async move{ 31 | Ok(resp) 32 | }; 33 | 34 | Box::pin(fut) 35 | } 36 | } 37 | 38 | impl AsyncTransporter { 39 | pub fn new(stream: Arc>) -> AsyncTransporter { 40 | AsyncTransporter{ 41 | stream: stream.clone() 42 | } 43 | } 44 | } 45 | 46 | impl Connection for AsyncTransporter { 47 | fn connected(&self) -> Connected { 48 | Connected::new() 49 | } 50 | } 51 | 52 | pub struct CustomResponse { 53 | stream: Arc>, 54 | } 55 | 56 | impl Connection for CustomResponse { 57 | fn connected(&self) -> Connected { 58 | Connected::new() 59 | } 60 | } 61 | 62 | const E: &str = "tried to unlock locked stream on custom response"; 63 | 64 | impl AsyncRead for CustomResponse { 65 | fn poll_read( 66 | self: Pin<&mut Self>, 67 | cx: &mut Context<'_>, 68 | buf: &mut tokio::io::ReadBuf<'_> 69 | ) -> Poll> { 70 | Pin::new(&mut *self.stream.try_lock().expect(E)).poll_read(cx, buf) 71 | } 72 | } 73 | 74 | impl AsyncWrite for CustomResponse { 75 | fn poll_write( 76 | self: Pin<&mut Self>, 77 | cx: &mut Context<'_>, 78 | buf: &[u8] 79 | ) -> Poll> { 80 | Pin::new(&mut *self.stream.try_lock().expect(E)).poll_write(cx, buf) 81 | } 82 | 83 | fn poll_flush( 84 | self: Pin<&mut Self>, 85 | cx: &mut Context<'_> 86 | ) -> Poll> { 87 | Pin::new(&mut *self.stream.try_lock().expect(E)).poll_flush(cx) 88 | } 89 | 90 | fn poll_shutdown( 91 | self: Pin<&mut Self>, 92 | cx: &mut Context<'_> 93 | ) -> Poll> 94 | { 95 | Pin::new(&mut *self.stream.try_lock().expect(E)).poll_shutdown(cx) 96 | } 97 | } 98 | 99 | fn main() { 100 | 101 | } 102 | -------------------------------------------------------------------------------- /examples/sync_tun.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Attention: this has nothing to do with async_smoltcp, it's just 3 | a simple smoltcp for testing purposes. It's here to facilitate testing 4 | 5 | 6 | To run this example, install below: 7 | 8 | apt-get install -y iproute2 iptables dnsutils 9 | 10 | then do this with sudo if outside of docker: 11 | 12 | ip tuntap add dev tun1 mode tun user `id -un` 13 | ip link set dev tun1 up 14 | ip addr add dev tun1 local 192.168.1.0 remote 192.168.1.1 15 | iptables -t filter -I FORWARD -i tun1 -o eth0 -j ACCEPT 16 | iptables -t filter -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 17 | iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE 18 | sysctl net.ipv4.ip_forward=1 19 | 20 | then 21 | 22 | cargo run --example async_tun --features="default log" -- http://neversll.com 80 23 | 24 | More information about the ip commands: https://serverfault.com/a/1067366/554980 25 | */ 26 | 27 | mod utils; 28 | 29 | use std::str::{self, FromStr}; 30 | use std::collections::BTreeMap; 31 | use std::os::unix::io::AsRawFd; 32 | use log::debug; 33 | 34 | use smoltcp::phy::{Device, Medium, wait as phy_wait}; 35 | use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr}; 36 | use smoltcp::iface::{NeighborCache, InterfaceBuilder, Routes}; 37 | use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; 38 | use smoltcp::time::Instant; 39 | 40 | fn main() { 41 | #[cfg(feature = "log")] 42 | utils::setup_logging(""); 43 | 44 | let (mut opts, mut free) = utils::create_options(); 45 | utils::add_tuntap_options(&mut opts, &mut free); 46 | utils::add_middleware_options(&mut opts, &mut free); 47 | free.push("ADDRESS"); 48 | free.push("PORT"); 49 | 50 | let mut matches = utils::parse_options(&opts, free); 51 | let device = utils::parse_tuntap_options(&mut matches); 52 | 53 | let fd = device.as_raw_fd(); 54 | let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false); 55 | let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); 56 | let port = u16::from_str(&matches.free[1]).expect("invalid port format"); 57 | 58 | let neighbor_cache = NeighborCache::new(BTreeMap::new()); 59 | 60 | let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 64]); 61 | let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 128]); 62 | let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); 63 | 64 | let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); 65 | let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24)]; 66 | let default_v4_gw = Ipv4Address::new(192, 168, 69, 100); 67 | let mut routes_storage = [None; 1]; 68 | let mut routes = Routes::new(&mut routes_storage[..]); 69 | routes.add_default_ipv4_route(default_v4_gw).unwrap(); 70 | 71 | let medium = device.capabilities().medium; 72 | let mut builder = InterfaceBuilder::new(device) 73 | .ip_addrs(ip_addrs) 74 | .routes(routes); 75 | if medium == Medium::Ethernet { 76 | builder = builder 77 | .ethernet_addr(ethernet_addr) 78 | .neighbor_cache(neighbor_cache); 79 | } 80 | let mut iface = builder.finalize(); 81 | 82 | let mut sockets = SocketSet::new(vec![]); 83 | let tcp_handle = sockets.add(tcp_socket); 84 | 85 | { 86 | let mut socket = sockets.get::(tcp_handle); 87 | socket.connect((address, port), 49500).unwrap(); 88 | } 89 | 90 | let mut tcp_active = false; 91 | loop { 92 | let timestamp = Instant::now(); 93 | match iface.poll(&mut sockets, timestamp) { 94 | Ok(_) => {}, 95 | Err(e) => { 96 | debug!("poll error: {}", e); 97 | } 98 | } 99 | 100 | { 101 | let mut socket = sockets.get::(tcp_handle); 102 | if socket.is_active() && !tcp_active { 103 | debug!("connected"); 104 | } else if !socket.is_active() && tcp_active { 105 | debug!("disconnected"); 106 | break 107 | } 108 | tcp_active = socket.is_active(); 109 | 110 | if socket.may_recv() { 111 | let data = socket.recv(|data| { 112 | let mut data = data.to_owned(); 113 | if !data.is_empty() { 114 | debug!("recv data: {:?}", 115 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); 116 | data = data.split(|&b| b == b'\n').collect::>().concat(); 117 | data.reverse(); 118 | data.extend(b"\n"); 119 | } 120 | (data.len(), data) 121 | }).unwrap(); 122 | if socket.can_send() && !data.is_empty() { 123 | debug!("send data: {:?}", 124 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); 125 | socket.send_slice(&data[..]).unwrap(); 126 | } 127 | } else if socket.may_send() { 128 | debug!("close"); 129 | socket.close(); 130 | } 131 | } 132 | 133 | phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /examples/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::cell::RefCell; 4 | use std::str::{self, FromStr}; 5 | use std::rc::Rc; 6 | use std::io::{self, Write}; 7 | use std::fs::File; 8 | use std::time::{SystemTime, UNIX_EPOCH}; 9 | use std::env; 10 | use std::process; 11 | #[cfg(feature = "log")] 12 | use log::{Level, LevelFilter, trace}; 13 | #[cfg(feature = "log")] 14 | use env_logger::Builder; 15 | use getopts::{Options, Matches}; 16 | 17 | use smoltcp::phy::{Device, Tracer, FaultInjector, Medium}; 18 | #[cfg(all(feature = "tap", feature = "tun"))] 19 | use smoltcp::phy::TunTapInterface; 20 | use smoltcp::phy::{PcapWriter, PcapSink, PcapMode}; 21 | use smoltcp::phy::RawSocket; 22 | use smoltcp::time::{Duration, Instant}; 23 | 24 | #[cfg(feature = "log")] 25 | pub fn setup_logging_with_clock(filter: &str, since_startup: F) 26 | where F: Fn() -> Instant + Send + Sync + 'static { 27 | Builder::new() 28 | .format(move |buf, record| { 29 | let elapsed = since_startup(); 30 | let timestamp = format!("[{}]", elapsed); 31 | if record.target().starts_with("smoltcp::") { 32 | writeln!(buf, "\x1b[0m{} ({}): {}\x1b[0m", timestamp, 33 | record.target().replace("smoltcp::", ""), record.args()) 34 | } else if record.level() == Level::Trace { 35 | let message = format!("{}", record.args()); 36 | writeln!(buf, "\x1b[37m{} {}\x1b[0m", timestamp, 37 | message.replace("\n", "\n ")) 38 | } else { 39 | writeln!(buf, "\x1b[32m{} ({}): {}\x1b[0m", timestamp, 40 | record.target(), record.args()) 41 | } 42 | }) 43 | .filter(None, LevelFilter::Trace) 44 | //.parse(filter) 45 | //.parse(&env::var("RUST_LOG").unwrap_or_else(|_| "".to_owned())) 46 | .init(); 47 | } 48 | 49 | #[cfg(feature = "log")] 50 | pub fn setup_logging(filter: &str) { 51 | setup_logging_with_clock(filter, move || { 52 | Instant::now() 53 | }) 54 | } 55 | 56 | pub fn create_options() -> (Options, Vec<&'static str>) { 57 | let mut opts = Options::new(); 58 | opts.optflag("h", "help", "print this help menu"); 59 | (opts, Vec::new()) 60 | } 61 | 62 | pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches { 63 | match options.parse(env::args().skip(1)) { 64 | Err(err) => { 65 | println!("{}", err); 66 | process::exit(1) 67 | } 68 | Ok(matches) => { 69 | if matches.opt_present("h") || matches.free.len() != free.len() { 70 | let brief = format!("Usage: {} [OPTION]... {}", 71 | env::args().next().unwrap(), free.join(" ")); 72 | print!("{}", options.usage(&brief)); 73 | process::exit(if matches.free.len() != free.len() { 1 } else { 0 }) 74 | } 75 | matches 76 | } 77 | } 78 | } 79 | 80 | pub fn add_tuntap_options(opts: &mut Options, _free: &mut Vec<&str>) { 81 | opts.optopt("", "tun", "TUN interface to use", "tun0"); 82 | opts.optopt("", "tap", "TAP interface to use", "tap0"); 83 | } 84 | 85 | #[cfg(all(feature = "tap", feature = "tun"))] 86 | pub fn parse_tuntap_options(matches: &mut Matches) -> TunTapInterface { 87 | let tun = matches.opt_str("tun"); 88 | let tap = matches.opt_str("tap"); 89 | match(tun,tap) { 90 | (Some(tun), None) => TunTapInterface::new(&tun, Medium::Ip).unwrap(), 91 | (None, Some(tap)) => TunTapInterface::new(&tap, Medium::Ethernet).unwrap(), 92 | _ => panic!("You must specify exactly one of --tun or --tap"), 93 | } 94 | } 95 | 96 | pub fn parse_raw_socket_options(matches: &mut Matches) -> RawSocket { 97 | let interface = matches.free.remove(0); 98 | RawSocket::new(&interface).unwrap() 99 | } 100 | 101 | pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) { 102 | opts.optopt("", "pcap", "Write a packet capture file", "FILE"); 103 | opts.optopt("", "drop-chance", "Chance of dropping a packet (%)", "CHANCE"); 104 | opts.optopt("", "corrupt-chance", "Chance of corrupting a packet (%)", "CHANCE"); 105 | opts.optopt("", "size-limit", "Drop packets larger than given size (octets)", "SIZE"); 106 | opts.optopt("", "tx-rate-limit", "Drop packets after transmit rate exceeds given limit \ 107 | (packets per interval)", "RATE"); 108 | opts.optopt("", "rx-rate-limit", "Drop packets after transmit rate exceeds given limit \ 109 | (packets per interval)", "RATE"); 110 | opts.optopt("", "shaping-interval", "Sets the interval for rate limiting (ms)", "RATE"); 111 | } 112 | 113 | pub fn parse_middleware_options(matches: &mut Matches, device: D, loopback: bool) 114 | -> FaultInjector>>> 115 | where D: for<'a> Device<'a> 116 | { 117 | let drop_chance = matches.opt_str("drop-chance").map(|s| u8::from_str(&s).unwrap()) 118 | .unwrap_or(0); 119 | let corrupt_chance = matches.opt_str("corrupt-chance").map(|s| u8::from_str(&s).unwrap()) 120 | .unwrap_or(0); 121 | let size_limit = matches.opt_str("size-limit").map(|s| usize::from_str(&s).unwrap()) 122 | .unwrap_or(0); 123 | let tx_rate_limit = matches.opt_str("tx-rate-limit").map(|s| u64::from_str(&s).unwrap()) 124 | .unwrap_or(0); 125 | let rx_rate_limit = matches.opt_str("rx-rate-limit").map(|s| u64::from_str(&s).unwrap()) 126 | .unwrap_or(0); 127 | let shaping_interval = matches.opt_str("shaping-interval").map(|s| u64::from_str(&s).unwrap()) 128 | .unwrap_or(0); 129 | 130 | let pcap_writer: Box; 131 | if let Some(pcap_filename) = matches.opt_str("pcap") { 132 | pcap_writer = Box::new(File::create(pcap_filename).expect("cannot open file")) 133 | } else { 134 | pcap_writer = Box::new(io::sink()) 135 | } 136 | 137 | let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); 138 | 139 | let device = PcapWriter::new( 140 | device, 141 | Rc::new(RefCell::new(pcap_writer)) as Rc, 142 | if loopback { PcapMode::TxOnly } else { PcapMode::Both }, 143 | ); 144 | 145 | let device = Tracer::new(device, |_timestamp, _printer| { 146 | #[cfg(feature = "log")] 147 | trace!("{}", _printer); 148 | }); 149 | 150 | let mut device = FaultInjector::new(device, seed); 151 | device.set_drop_chance(drop_chance); 152 | device.set_corrupt_chance(corrupt_chance); 153 | device.set_max_packet_size(size_limit); 154 | device.set_max_tx_rate(tx_rate_limit); 155 | device.set_max_rx_rate(rx_rate_limit); 156 | device.set_bucket_interval(Duration::from_millis(shaping_interval)); 157 | device 158 | } 159 | 160 | fn main(){ 161 | 162 | } -------------------------------------------------------------------------------- /src/async_smoltcp.rs: -------------------------------------------------------------------------------- 1 | use super::virtual_tun::VirtualTunInterface as VirtualTunDevice; 2 | #[cfg(feature = "vpn")] 3 | use super::virtual_tun::{VirtualTunReadError, VirtualTunWriteError}; 4 | use core::task::{Context, Poll, Waker}; 5 | use crossbeam_channel::{bounded, unbounded}; 6 | use crossbeam_channel::{Receiver, SendError, Sender, TryRecvError}; 7 | use futures::executor::block_on; 8 | use futures::lock::Mutex as FutMutex; 9 | #[cfg(feature = "log")] 10 | #[allow(unused_imports)] 11 | use log::{debug, error, info, trace, warn}; 12 | #[cfg(feature = "simple_socket")] 13 | use simple_socket::{ 14 | Packet, SocketConnectionError, SocketSendError, SocketType, TcpPacket, UdpPacket, 15 | }; 16 | #[cfg(feature = "vpn")] 17 | use simple_vpn::{ 18 | PhyReceiveError, 19 | PhySendError, 20 | VpnClient, 21 | //VpnConnectionError,VpnDisconnectionError, 22 | }; 23 | use smoltcp::iface::{Interface, InterfaceBuilder, Routes, SocketHandle}; 24 | use smoltcp::phy::{Device, Medium, TunTapInterface}; 25 | use smoltcp::time::Instant; 26 | pub use smoltcp::wire::IpAddress; 27 | pub use smoltcp::{Error as SmoltcpError, Result as SmoltcpResult}; 28 | use std::collections::BTreeMap; 29 | use std::collections::HashMap; 30 | use std::collections::VecDeque; 31 | use std::net::SocketAddr; 32 | use std::sync::atomic::AtomicBool; 33 | use std::sync::atomic::Ordering; 34 | use std::time::Duration; 35 | //use smoltcp::phy::TunInterface as TunDevice; 36 | use smoltcp::socket::{TcpSocket, TcpSocketBuffer, UdpSocket, UdpSocketBuffer}; 37 | pub use smoltcp::wire::{IpCidr, IpEndpoint, Ipv4Address, Ipv6Address}; 38 | use std::net::IpAddr; 39 | use std::pin::Pin; 40 | use std::sync::Arc; 41 | #[cfg(feature = "async")] 42 | use tokio::io::{AsyncRead, AsyncWrite}; 43 | 44 | #[allow(unused)] 45 | use crate::DEFAULT_MTU; 46 | 47 | pub enum PollWaitError { 48 | NoPollWait, 49 | } 50 | 51 | pub type OnPollWait = Arc std::result::Result<(), PollWaitError> + Send + Sync>; 52 | 53 | #[derive(Debug)] 54 | pub enum SpinError { 55 | NoCallback, 56 | NoSocket, 57 | ClosedSocket, 58 | Unknown(String), 59 | } 60 | 61 | const MAX_PACKETS_CHANNEL: usize = 10; 62 | 63 | #[doc(hidden)] 64 | pub fn __feed__(arg: T, f: impl FnOnce(T) -> R) -> R { 65 | f(arg) 66 | } 67 | 68 | macro_rules! choose_device { 69 | ( 70 | $scrutinee:expr, $closure:expr $(,)? 71 | ) => {{ 72 | use $crate::async_smoltcp::SmolStackWithDevice; 73 | use $crate::async_smoltcp::__feed__; 74 | 75 | match $scrutinee { 76 | SmolStackWithDevice::VirtualTun(inner) => __feed__(inner, $closure), 77 | SmolStackWithDevice::Tun(inner) => __feed__(inner, $closure), 78 | SmolStackWithDevice::Tap(inner) => __feed__(inner, $closure), 79 | } 80 | }}; 81 | } 82 | 83 | pub struct SmolStack 84 | where 85 | DeviceT: for<'d> Device<'d>, 86 | { 87 | //pub sockets: Vec, 88 | pub interface: Interface<'static, DeviceT>, 89 | //`Sender>` sends data to socket, `Receiver>` received data from socket 90 | socket_handles: HashMap< 91 | SocketHandle, 92 | ( 93 | SocketType, 94 | Sender>, 95 | Receiver>, 96 | Arc>>, 97 | ), 98 | >, 99 | should_stack_thread_stop: Arc, 100 | #[allow(unused)] 101 | read_wake_deque: Option>>>, 102 | write_wake_deque: Option>>>, 103 | } 104 | 105 | pub enum SmolStackWithDevice { 106 | VirtualTun(SmolStack), 107 | Tun(SmolStack), 108 | Tap(SmolStack), 109 | } 110 | 111 | impl SmolStackWithDevice { 112 | pub fn spawn_stack_thread(stack: Arc>) { 113 | std::thread::Builder::new() 114 | .name("stack_thread".to_string()) 115 | .spawn(move || { 116 | let stack = stack.clone(); 117 | //let write_wake_deque_ = block_on(stack.clone().lock()).get_write_wake_deque(); 118 | loop { 119 | block_on(stack.clone().lock()).poll().unwrap(); 120 | if let Err(_err) = block_on(stack.clone().lock()).spin_all() { 121 | #[cfg(feature = "log")] 122 | error!("{:?}", _err); 123 | } 124 | //TODO: use wake deque future FutMutex or normal FutMutex? 125 | /* 126 | let waker = block_on(write_wake_deque_.clone().lock()).pop_front(); 127 | match waker { 128 | Some(waker) => { 129 | //info!("waking from write wake deque"); 130 | waker.wake() 131 | } 132 | None => {} 133 | } 134 | */ 135 | std::thread::sleep(std::time::Duration::from_millis(35)); 136 | if block_on(stack.clone().lock()).should_stop() { 137 | #[cfg(feature = "log")] 138 | info!("end of stack_thread"); 139 | break; 140 | } 141 | } 142 | }) 143 | .unwrap(); 144 | } 145 | } 146 | 147 | impl SmolStackWithDevice { 148 | #[cfg(feature = "vpn")] 149 | pub fn new_from_vpn( 150 | interface_name: &str, 151 | //address: Option, 152 | default_v4_gateway: Option, 153 | default_v6_gateway: Option, 154 | mtu: Option, 155 | ip_addrs: Option>, 156 | vpn_client: Arc>, 157 | ) -> SmolStackWithDevice { 158 | let mtu = mtu.unwrap_or(DEFAULT_MTU); 159 | let vpn_client_ = vpn_client.clone(); 160 | let on_virtual_tun_read = Arc::new( 161 | move |buffer: &mut [u8]| -> std::result::Result { 162 | let mut size = 0; 163 | //Since there should be very few or no more than one IP stacks 164 | //connected to the same VPN client, it's no big deal to block here 165 | let mut vpn_client_ = block_on(vpn_client_.lock()); 166 | match vpn_client_.phy_receive(None, &mut |openvpn_buffer: &[u8]| { 167 | size = openvpn_buffer.len(); 168 | for i in 0..size { 169 | buffer[i] = openvpn_buffer[i] 170 | } 171 | #[cfg(feature = "packet_log")] 172 | crate::packet_log::log(&buffer[..size], true); 173 | }) { 174 | Ok(s) => Ok(s), 175 | Err(PhyReceiveError::NoDataAvailable) => Err(VirtualTunReadError::WouldBlock), 176 | //TODO: do not panic below and treat the error? 177 | Err(PhyReceiveError::Unknown(error_string)) => { 178 | panic!("openvpn_client.receive() unknown error: {}", error_string); 179 | } 180 | } 181 | }, 182 | ); 183 | 184 | let vpn_client_ = vpn_client.clone(); 185 | let on_virtual_tun_write = Arc::new( 186 | move |f: &mut dyn FnMut(&mut [u8]), 187 | size: usize| 188 | -> std::result::Result { 189 | let mut buffer = vec![0; size]; 190 | f(buffer.as_mut_slice()); 191 | #[cfg(feature = "packet_log")] 192 | crate::packet_log::log(buffer.as_slice(), false); 193 | //Since there should be very few or no more than one IP stacks 194 | //connected to the same VPN client, it's no big deal to block here 195 | match block_on(vpn_client_.lock()).phy_send(buffer.as_slice()) { 196 | Ok(s) => Ok(s), 197 | Err(PhySendError::Unknown(error_string)) => { 198 | //TODO: not panic here, just treat the error 199 | panic!("{}", error_string); 200 | } 201 | } 202 | }, 203 | ); 204 | 205 | let _on_poll_wait = Arc::new( 206 | |_duration: std::time::Duration| -> std::result::Result<(), PollWaitError> { Ok(()) }, 207 | ); 208 | 209 | let device = VirtualTunDevice::new( 210 | interface_name, 211 | on_virtual_tun_read, 212 | on_virtual_tun_write, 213 | mtu, 214 | ) 215 | .unwrap(); 216 | 217 | let default_ip_address = IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24); 218 | let mut routes = Routes::new(BTreeMap::new()); 219 | 220 | let default_v4_gateway = default_v4_gateway.unwrap_or(Ipv4Address::new(192, 168, 69, 100)); 221 | 222 | routes.add_default_ipv4_route(default_v4_gateway).unwrap(); 223 | 224 | if default_v6_gateway.is_some() { 225 | //TODO: find a good ipv6 to use 226 | let default_v6_gateway = 227 | default_v6_gateway.unwrap_or(Ipv6Address::new(1, 1, 1, 1, 1, 1, 1, 1)); 228 | routes.add_default_ipv6_route(default_v6_gateway).unwrap(); 229 | } 230 | let ip_addrs = ip_addrs.unwrap_or(vec![default_ip_address]); 231 | let interface = InterfaceBuilder::new(device) 232 | .ip_addrs(ip_addrs) 233 | .routes(routes) 234 | .finalize(); 235 | 236 | let socket_set = SocketSet::new(vec![]); 237 | 238 | SmolStackWithDevice::VirtualTun(SmolStack { 239 | sockets: socket_set, 240 | interface: interface, 241 | socket_handles: HashMap::new(), 242 | should_stack_thread_stop: Arc::new(AtomicBool::new(false)), 243 | read_wake_deque: None, 244 | write_wake_deque: None, 245 | }) 246 | } 247 | 248 | #[cfg(feature = "tap")] 249 | pub fn new_tap( 250 | interface_name: &str, 251 | ip_addrs: Option>, 252 | default_v4_gateway: Option, 253 | default_v6_gateway: Option, 254 | //mtu: Option, 255 | ) -> SmolStackWithDevice { 256 | //let mtu = mtu.unwrap_or(DEFAULT_MTU); 257 | 258 | let device = TunTapInterface::new(interface_name, Medium::Ethernet).unwrap(); 259 | let ip_addrs = ip_addrs.unwrap_or(vec![IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24)]); 260 | let mut routes = Routes::new(BTreeMap::new()); 261 | //let ip_addrs = ip_addrs.unwrap_or(vec![default_ip_address]); 262 | 263 | let default_v4_gateway = default_v4_gateway.unwrap_or(Ipv4Address::new(192, 168, 69, 100)); 264 | 265 | routes.add_default_ipv4_route(default_v4_gateway).unwrap(); 266 | 267 | if default_v6_gateway.is_some() { 268 | //TODO: find a good ipv6 to use 269 | let default_v6_gateway = 270 | default_v6_gateway.unwrap_or(Ipv6Address::new(1, 1, 1, 1, 1, 1, 1, 1)); 271 | routes.add_default_ipv6_route(default_v6_gateway).unwrap(); 272 | } 273 | let interface = InterfaceBuilder::new(device, vec![]) 274 | .ip_addrs(ip_addrs) 275 | .routes(routes) 276 | .finalize(); 277 | 278 | //let socket_set = SocketSet::new(vec![]); 279 | 280 | SmolStackWithDevice::Tap(SmolStack { 281 | //sockets: socket_set, 282 | interface: interface, 283 | socket_handles: HashMap::new(), 284 | should_stack_thread_stop: Arc::new(AtomicBool::new(false)), 285 | read_wake_deque: None, 286 | write_wake_deque: None, 287 | }) 288 | } 289 | 290 | #[cfg(feature = "tun")] 291 | pub fn new_tun( 292 | interface_name: &str, 293 | default_v4_gateway: Option, 294 | default_v6_gateway: Option, 295 | ip_addrs: Option>, 296 | ) -> SmolStackWithDevice { 297 | let device = TunTapInterface::new(interface_name, Medium::Ip).unwrap(); 298 | 299 | let default_ip_address = IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24); 300 | let mut routes = Routes::new(BTreeMap::new()); 301 | let ip_addrs = ip_addrs.unwrap_or(vec![default_ip_address]); 302 | let default_v4_gateway = default_v4_gateway.unwrap_or(Ipv4Address::new(192, 168, 69, 100)); 303 | 304 | routes.add_default_ipv4_route(default_v4_gateway).unwrap(); 305 | 306 | if default_v6_gateway.is_some() { 307 | //TODO: find a good ipv6 to use 308 | let default_v6_gateway = 309 | default_v6_gateway.unwrap_or(Ipv6Address::new(1, 1, 1, 1, 1, 1, 1, 1)); 310 | routes.add_default_ipv6_route(default_v6_gateway).unwrap(); 311 | } 312 | 313 | let interface = InterfaceBuilder::new(device, vec![]) 314 | .ip_addrs(ip_addrs) 315 | .routes(routes) 316 | .finalize(); 317 | 318 | //let socket_set = SocketSet::new(vec![]); 319 | 320 | SmolStackWithDevice::Tun(SmolStack { 321 | //sockets: socket_set, 322 | interface: interface, 323 | socket_handles: HashMap::new(), 324 | should_stack_thread_stop: Arc::new(AtomicBool::new(false)), 325 | read_wake_deque: None, 326 | write_wake_deque: None, 327 | }) 328 | } 329 | 330 | #[allow(unused)] 331 | fn get_write_wake_deque(&self) -> Arc>> { 332 | choose_device!(self, |stack| stack 333 | .write_wake_deque 334 | .as_ref() 335 | .unwrap() 336 | .clone()) 337 | } 338 | 339 | fn should_stop(&self) -> bool { 340 | choose_device!(self, |stack| { 341 | stack.should_stack_thread_stop.load(Ordering::Relaxed) 342 | }) 343 | } 344 | 345 | pub fn close_socket(&mut self, socket: SocketHandle) { 346 | choose_device!(self, |stack_| { 347 | stack_.interface.remove_socket(socket); 348 | }); 349 | } 350 | 351 | pub fn add_tcp_socket(&mut self) -> Result { 352 | choose_device!(self, |stack_| { 353 | let rx_buffer = TcpSocketBuffer::new(vec![0; 65000]); 354 | let tx_buffer = TcpSocketBuffer::new(vec![0; 65000]); 355 | let socket = TcpSocket::new(rx_buffer, tx_buffer); 356 | let handle = stack_.interface.add_socket(socket); 357 | 358 | let (socket_tx, stack_rx_from_socket) = bounded(MAX_PACKETS_CHANNEL); 359 | let (stack_tx_to_socket, socket_rx) = bounded(MAX_PACKETS_CHANNEL); 360 | 361 | let waker = Arc::new(FutMutex::new(None)); 362 | 363 | stack_.socket_handles.insert( 364 | handle, 365 | ( 366 | SocketType::TCP, 367 | stack_tx_to_socket, 368 | stack_rx_from_socket, 369 | waker.clone(), 370 | ), 371 | ); 372 | let smol_socket = SmolSocket { 373 | handle: handle, 374 | channel: Some((socket_tx, socket_rx)), 375 | current: None, 376 | waker: waker.clone(), 377 | }; 378 | Ok(smol_socket) 379 | }) 380 | } 381 | 382 | pub fn add_udp_socket(&mut self) -> Result { 383 | choose_device!(self, |stack_| { 384 | let rx_buffer = UdpSocketBuffer::new(Vec::new(), vec![0; 1024]); 385 | let tx_buffer = UdpSocketBuffer::new(Vec::new(), vec![0; 1024]); 386 | let socket = UdpSocket::new(rx_buffer, tx_buffer); 387 | let handle = stack_.interface.add_socket(socket); 388 | 389 | let (socket_tx, stack_rx_from_socket) = unbounded(); 390 | let (stack_tx_to_socket, socket_rx) = unbounded(); 391 | 392 | let waker = Arc::new(FutMutex::new(None)); 393 | 394 | stack_.socket_handles.insert( 395 | handle, 396 | ( 397 | SocketType::UDP, 398 | stack_tx_to_socket, 399 | stack_rx_from_socket, 400 | waker.clone(), 401 | ), 402 | ); 403 | let smol_socket = SmolSocket { 404 | handle: handle, 405 | channel: Some((socket_tx, socket_rx)), 406 | current: None, 407 | waker: waker.clone(), 408 | }; 409 | Ok(smol_socket) 410 | }) 411 | } 412 | 413 | /* 414 | //TODO: dreprecate it since not used? 415 | pub fn tcp_socket_send( 416 | &mut self, 417 | socket_handle: SocketHandle, 418 | data: &[u8], 419 | ) -> Result { 420 | choose_device!(self, |stack| { 421 | let mut socket = stack.sockets.get::(socket_handle); 422 | socket 423 | .send_slice(data) 424 | .map_err(|e| SocketSendError::Unknown("".into())) 425 | }) 426 | } 427 | 428 | //TODO: deprecate it since not used? 429 | pub fn udp_socket_send( 430 | &mut self, 431 | socket_handle: SocketHandle, 432 | data: &[u8], 433 | addr: SocketAddr, 434 | ) -> Result { 435 | choose_device!(self, |stack| { 436 | let mut socket = stack.sockets.get::(socket_handle); 437 | socket 438 | .send_slice(data, addr.into()) 439 | .map(|_| data.len()) 440 | .map_err(|e| SocketSendError::Unknown("".into())) 441 | }) 442 | } 443 | */ 444 | 445 | pub fn tcp_connect( 446 | &mut self, 447 | handle: &SocketHandle, 448 | addr: SocketAddr, 449 | src_port: u16, 450 | ) -> Result<(), SocketConnectionError> { 451 | choose_device!(self, |stack| { 452 | let (socket, context) = stack.interface.get_socket_and_context::(handle.clone()); 453 | socket 454 | .connect(context, addr, src_port) 455 | .map_err(|e| smol_e_connection(e)) 456 | }) 457 | } 458 | 459 | pub fn poll(&mut self) -> SmoltcpResult { 460 | choose_device!(self, |stack| { 461 | let timestamp = Instant::now(); 462 | match stack.interface.poll(timestamp) { 463 | Ok(b) => Ok(b), 464 | Err(e) => { 465 | panic!("{}", e); 466 | } 467 | } 468 | }) 469 | } 470 | 471 | pub fn spin_tcp( 472 | socket: &mut TcpSocket, 473 | send_to_socket: &Sender>, 474 | receive_from_socket: &Receiver>, 475 | waker: &Arc>>, //on_tcp_socket_data: OnTcpSocketData, 476 | ) -> std::result::Result<(), SpinError> { 477 | if socket.can_recv() { 478 | let r = socket.recv(|data| { 479 | let p = Arc::new(Packet::new_tcp(TcpPacket { 480 | data: data.to_vec(), 481 | })); 482 | match send_to_socket.send(p) { 483 | Ok(()) => {} 484 | Err(SendError(_message)) => { 485 | //If we arrived here it probably means the channel got disconnected, 486 | //return Err(SpinError::ClosedSocket); 487 | //return (0, Err(smoltcp::Error::Dropped)); 488 | #[cfg(feature = "log")] 489 | error!("spin_tcp: send_to_socket error"); 490 | } 491 | } 492 | block_on(waker.lock()).as_ref().map(|w| w.clone().wake()); 493 | (data.len(), ()) 494 | }); 495 | if let Err(_r) = r { 496 | #[cfg(feature = "log")] 497 | error!("spin_tcp error: {}", _r); 498 | } 499 | } 500 | if socket.can_send() { 501 | match receive_from_socket.try_recv() { 502 | Ok(packet) => { 503 | let p = packet.tcp.as_ref().unwrap().data.as_slice(); 504 | #[cfg(feature = "log")] 505 | debug!("C -> S: {:?}", std::str::from_utf8(p).unwrap()); 506 | socket.send_slice(p).map_err(|e| smol_e_send(e)).unwrap(); 507 | } 508 | Err(TryRecvError::Disconnected) => { 509 | #[cfg(feature = "log")] 510 | debug!("socket close"); 511 | socket.close(); 512 | } 513 | Err(TryRecvError::Empty) => { 514 | //#[cfg(feature = "log")] 515 | //info!("received empty packet"); 516 | } 517 | } 518 | } else { 519 | #[cfg(feature = "trace")] 520 | debug!("socket cannot send"); 521 | } 522 | Ok(()) 523 | } 524 | 525 | pub fn spin_udp( 526 | socket: &mut UdpSocket, 527 | send_to_socket: &Sender>, 528 | receive_from_socket: &Receiver>, 529 | waker: &Arc>>, //on_udp_socket_data: OnUdpSocketData, 530 | ) -> std::result::Result<(), SpinError> { 531 | if socket.can_recv() { 532 | if let Ok((buffer, endpoint)) = socket.recv() { 533 | let addr: IpAddr = match endpoint.addr { 534 | IpAddress::Ipv4(ipv4) => IpAddr::V4(ipv4.into()), 535 | IpAddress::Ipv6(ipv6) => IpAddr::V6(ipv6.into()), 536 | _ => return Err(SpinError::Unknown("spin address conversion error".into())), 537 | }; 538 | let port = endpoint.port; 539 | let p = Arc::new(Packet::new_udp(UdpPacket { 540 | data: buffer.to_vec(), 541 | from_ip: addr, 542 | from_port: port, 543 | })); 544 | match send_to_socket.send(p) { 545 | Ok(()) => {} 546 | Err(SendError(_message)) => { 547 | //If we arrived here it probably means the channel got disconnected, 548 | //so let's close this socket 549 | #[cfg(feature = "log")] 550 | debug!("socket close"); 551 | socket.close(); 552 | } 553 | } 554 | block_on(waker.lock()).as_ref().map(|w| w.clone().wake()); 555 | } 556 | } 557 | if socket.can_send() { 558 | match receive_from_socket.try_recv() { 559 | Ok(packet) => { 560 | let s = packet.udp.as_ref().unwrap().data.as_slice(); 561 | let addr = packet.udp.as_ref().unwrap().from_ip; 562 | let port = packet.udp.as_ref().unwrap().from_port; 563 | let endpoint = std::net::SocketAddr::new(addr, port); 564 | socket 565 | .send_slice(s, endpoint.into()) 566 | .map(|_| s.len()) 567 | .map_err(|e| smol_e_send(e)) 568 | //TODO: no uwnrap 569 | .unwrap(); 570 | } 571 | Err(TryRecvError::Disconnected) => { 572 | #[cfg(feature = "log")] 573 | debug!("socket close"); 574 | socket.close(); 575 | } 576 | Err(TryRecvError::Empty) => {} 577 | } 578 | } else { 579 | #[cfg(feature = "log")] 580 | trace!("socket cannot send"); 581 | } 582 | 583 | Ok(()) 584 | } 585 | 586 | pub fn spin_all(&mut self) -> std::result::Result<(), SpinError> { 587 | choose_device!(self, |stack| { 588 | let mut smol_socket_handles = Vec::::new(); 589 | 590 | //TODO: this is not fast 591 | for (smol_socket_handle, _) in stack.socket_handles.iter() { 592 | smol_socket_handles.push(smol_socket_handle.clone()); 593 | } 594 | 595 | for smol_socket_handle in smol_socket_handles.iter_mut() { 596 | let (socket_type, send_to_socket, receive_from_socket, waker) = stack 597 | .socket_handles 598 | .get(&smol_socket_handle) 599 | .ok_or(SpinError::NoSocket) 600 | .unwrap(); 601 | match socket_type { 602 | SocketType::TCP => { 603 | let (mut socket, _context) = stack.interface.get_socket_and_context::(smol_socket_handle.clone()); 604 | let s = SmolStackWithDevice::spin_tcp( 605 | &mut socket, 606 | send_to_socket, 607 | receive_from_socket, 608 | &waker.clone(), //on_tcp_socket_data, 609 | ); 610 | 611 | if let Err(_s) = s { 612 | #[cfg(feature = "log")] 613 | debug!("spin_tcp error: {:?}", _s); 614 | socket.close(); 615 | } 616 | } 617 | SocketType::UDP => { 618 | let (mut socket, _context) = stack.interface.get_socket_and_context::(smol_socket_handle.clone()); 619 | let s = SmolStackWithDevice::spin_udp( 620 | &mut socket, 621 | send_to_socket, 622 | receive_from_socket, 623 | &waker.clone(), //on_udp_socket_data, 624 | ); 625 | if let Err(_s) = s { 626 | #[cfg(feature = "log")] 627 | debug!("spin_udp error: {:?}", _s); 628 | socket.close(); 629 | } 630 | } 631 | _ => unimplemented!("socket type not implemented yet"), 632 | } 633 | } 634 | }); 635 | Ok(()) 636 | } 637 | } 638 | 639 | pub struct SmolSocket { 640 | pub handle: SocketHandle, 641 | channel: Option<(Sender>, Receiver>)>, 642 | waker: Arc>>, 643 | //If we could not deliver an entire `Packet` in `poll_read`, we store it here for the next iteration 644 | current: Option<(Arc, usize)>, 645 | } 646 | 647 | #[cfg(feature = "async")] 648 | pub trait AsyncRW: tokio::io::AsyncRead + tokio::io::AsyncWrite {} 649 | 650 | #[cfg(feature = "async")] 651 | impl AsyncRW for T where T: AsyncRead + AsyncWrite {} 652 | 653 | #[cfg(feature = "async")] 654 | impl AsyncRead for SmolSocket { 655 | fn poll_read( 656 | self: Pin<&mut Self>, 657 | cx: &mut Context<'_>, 658 | buf: &mut tokio::io::ReadBuf<'_>, 659 | ) -> Poll> { 660 | let channel = match &self.channel { 661 | Some((a, b)) => (a, b), 662 | None => { 663 | //If I'm right this should never arrive 664 | panic!("this should't have happen, I guess"); 665 | //return Poll::Ready(Ok(())); 666 | } 667 | }; 668 | let packet = match channel.1.try_recv() { 669 | Ok(packet) => { 670 | #[cfg(feature = "log")] 671 | debug!( 672 | "C <- S: {}", 673 | std::str::from_utf8(&packet.tcp.as_ref().unwrap().data.as_slice()).unwrap() 674 | ); 675 | packet 676 | } 677 | Err(TryRecvError::Empty) => { 678 | //blocking here is no big deal since it's just to replace the variable. Very fast 679 | block_on(self.waker.lock()).replace(cx.waker().clone()); 680 | return Poll::Pending; 681 | } 682 | Err(TryRecvError::Disconnected) => { 683 | //TODO: return what in this case? 684 | block_on(self.waker.lock()).replace(cx.waker().clone()); 685 | return Poll::Pending; 686 | } 687 | }; 688 | 689 | let mut consume = |packet: &Arc, consumed: &mut usize| { 690 | if let Some(tcp_packet) = &packet.tcp { 691 | if tcp_packet.data.len() <= buf.remaining() { 692 | buf.put_slice(tcp_packet.data.as_slice()); 693 | Poll::Ready(Ok(())) 694 | } else { 695 | let remaining = buf.remaining(); 696 | buf.put_slice(&tcp_packet.data.as_slice()[*consumed..remaining]); 697 | //packet.index = consumed; 698 | //*current = Some((packet.clone(), remaining)); 699 | *consumed = remaining; 700 | //buf.advance(remaining) 701 | Poll::Ready(Ok(())) 702 | } 703 | } else { 704 | Poll::Ready(Err(std::io::Error::new( 705 | std::io::ErrorKind::Other, 706 | "invalid socket access", 707 | ))) 708 | } 709 | }; 710 | 711 | //If there's already a leftover buffer from the previous iteration 712 | if let Some((packet, mut consumed)) = &self.current { 713 | return consume(&packet, &mut consumed); 714 | } else { 715 | return consume(&packet, &mut 0); 716 | } 717 | } 718 | } 719 | 720 | #[cfg(feature = "async")] 721 | impl AsyncWrite for SmolSocket { 722 | fn poll_write( 723 | self: Pin<&mut Self>, 724 | _cx: &mut Context<'_>, 725 | buf: &[u8], 726 | ) -> Poll> { 727 | let channel = match &self.channel { 728 | Some((a, b)) => (a, b), 729 | None => { 730 | return Poll::Ready(Ok(0)); 731 | } 732 | }; 733 | channel 734 | .0 735 | .send(Arc::new(Packet::new_tcp(TcpPacket { data: buf.to_vec() }))) 736 | .unwrap(); 737 | Poll::Ready(Ok(buf.len())) 738 | } 739 | 740 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 741 | Poll::Ready(Ok(())) 742 | } 743 | 744 | fn poll_shutdown( 745 | self: Pin<&mut Self>, 746 | _cx: &mut Context<'_>, 747 | ) -> Poll> { 748 | let mut p = Pin::new(self); 749 | p.channel.take(); 750 | Poll::Ready(Ok(())) 751 | } 752 | } 753 | 754 | fn smol_e_send(e: smoltcp::Error) -> SocketSendError { 755 | match e { 756 | smoltcp::Error::Exhausted => SocketSendError::Exhausted, 757 | _ => SocketSendError::Unknown(format!("{}", e)), 758 | } 759 | } 760 | 761 | fn smol_e_connection(e: smoltcp::Error) -> SocketConnectionError { 762 | match e { 763 | smoltcp::Error::Exhausted => SocketConnectionError::Exhausted, 764 | _ => SocketConnectionError::Unknown(format!("{}", e)), 765 | } 766 | } 767 | 768 | /* 769 | fn smol_e_receive(e: smoltcp::Error) -> SocketReceiveError { 770 | match e { 771 | smoltcp::Error::Exhausted => SocketReceiveError::SocketNotReady, 772 | _ => SocketReceiveError::Unknown(format!("{}", e)) 773 | } 774 | } 775 | */ 776 | 777 | pub type SafeSmolStackWithDevice = SmolStackWithDevice; 778 | 779 | //TODO: instead, implement Send for the C types? 780 | unsafe impl Send for SafeSmolStackWithDevice {} 781 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod async_smoltcp; 2 | mod virtual_tun; 3 | pub(crate) mod packet_log; 4 | pub use async_smoltcp::*; 5 | use rand::Rng; // 0.8.0 6 | //TODO: synchronize this MTU with the VPN's MTU 7 | pub const DEFAULT_MTU: usize = 1500; 8 | 9 | pub fn random_source_port() -> u16 10 | { 11 | //TODO: better numbers 12 | rand::thread_rng().gen_range(42000..65000) 13 | } -------------------------------------------------------------------------------- /src/packet_log.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "packet_log")] 2 | 3 | #[allow(unused_imports)] 4 | use log::{debug, error, info, warn}; 5 | #[cfg(feature = "packet_log")] 6 | 7 | pub fn log(buffer: &[u8], read: bool) { 8 | use etherparse::{IpHeader, TransportHeader}; 9 | if read { 10 | debug!("PHY <<<<"); 11 | } else { 12 | debug!("PHY >>>>"); 13 | } 14 | match etherparse::PacketHeaders::from_ip_slice(buffer) { 15 | Err(value) => error!("ip packet parsing error {:?}", value), 16 | Ok(value) => { 17 | let ip_src_dest = |_ip: &IpHeader| { 18 | match value.ip.as_ref().unwrap() { 19 | IpHeader::Version4(ipv4) => ( 20 | format!("{:?}", &ipv4.source), 21 | format!("{:?}", &ipv4.destination), 22 | ), 23 | IpHeader::Version6(ipv6) => ( 24 | format!("{:?}", &ipv6.source), 25 | format!("{:?}", &ipv6.destination), 26 | ), 27 | } 28 | }; 29 | 30 | let is_tcp = |transport: &TransportHeader| { 31 | match transport { 32 | TransportHeader::Tcp(_) => true, 33 | TransportHeader::Udp(_) => false, 34 | } 35 | }; 36 | 37 | let transport_src_dest = |transport: &TransportHeader| { 38 | match transport { 39 | TransportHeader::Tcp(tcp) => (tcp.source_port, tcp.destination_port), 40 | TransportHeader::Udp(udp) => (udp.source_port, udp.destination_port), 41 | } 42 | }; 43 | 44 | let syn_ack = |transport: &TransportHeader| { 45 | match transport { 46 | TransportHeader::Tcp(tcp) => (tcp.syn, tcp.ack), 47 | _ => (false, false), 48 | } 49 | }; 50 | 51 | let ip_src_dest_val = ip_src_dest(&value.ip.as_ref().unwrap()); 52 | debug!("IP {} -> {}", ip_src_dest_val.0, ip_src_dest_val.1); 53 | 54 | let v = value.transport.as_ref().unwrap(); 55 | let trasnsport_src_dest_val = transport_src_dest(v); 56 | 57 | if is_tcp(v) { 58 | let syn_ack_val = syn_ack(v); 59 | 60 | let syn_ack_text = if value.payload.len() == 0 { 61 | format!("syn: {}, ack: {}", syn_ack_val.0, syn_ack_val.1) 62 | } else { 63 | "".to_string() 64 | }; 65 | debug!( 66 | "TCP: {} -> {} {}", 67 | trasnsport_src_dest_val.0, trasnsport_src_dest_val.1, syn_ack_text 68 | ); 69 | } else { 70 | debug!( 71 | "UDP: {} -> {}", 72 | trasnsport_src_dest_val.0, trasnsport_src_dest_val.1, 73 | ); 74 | } 75 | 76 | if value.payload.len() > 0 { 77 | debug!("payload: {:?}", String::from_utf8_lossy(value.payload)); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/virtual_tun.rs: -------------------------------------------------------------------------------- 1 | use smoltcp::phy::{self, Device, DeviceCapabilities, Medium}; 2 | use smoltcp::time::Instant; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | use std::sync::{Arc}; 6 | use std::vec::Vec; 7 | 8 | pub enum VirtualTunReadError { 9 | WouldBlock 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum VirtualTunWriteError { 14 | NoData 15 | } 16 | 17 | pub type OnVirtualTunRead = Arc Result + Send + Sync>; 18 | pub type OnVirtualTunWrite = Arc< 19 | dyn Fn(&mut dyn FnMut(&mut [u8]), usize) -> Result + Send + Sync, 20 | >; 21 | 22 | #[derive(Clone)] 23 | pub struct VirtualTunInterface { 24 | mtu: usize, 25 | //has_data: Arc<(Mutex<()>, Condvar)>, 26 | on_virtual_tun_read: OnVirtualTunRead, 27 | on_virtual_tun_write: OnVirtualTunWrite, 28 | } 29 | 30 | impl<'a> VirtualTunInterface { 31 | pub fn new( 32 | _name: &str, 33 | on_virtual_tun_read: OnVirtualTunRead, 34 | on_virtual_tun_write: OnVirtualTunWrite, 35 | mtu: usize 36 | ) -> smoltcp::Result { 37 | Ok(VirtualTunInterface { 38 | mtu: mtu, 39 | on_virtual_tun_read: on_virtual_tun_read, 40 | on_virtual_tun_write: on_virtual_tun_write, 41 | }) 42 | } 43 | } 44 | 45 | impl<'d> Device<'d> for VirtualTunInterface { 46 | type RxToken = RxToken; 47 | type TxToken = TxToken; 48 | 49 | fn capabilities(&self) -> DeviceCapabilities { 50 | let mut d = DeviceCapabilities::default(); 51 | d.max_transmission_unit = self.mtu; 52 | d.medium = Medium::Ip; 53 | d 54 | } 55 | 56 | fn receive(&'d mut self) -> Option<(Self::RxToken, Self::TxToken)> { 57 | let mut buffer = vec![0; self.mtu]; 58 | let r = (self.on_virtual_tun_read)(buffer.as_mut_slice()); 59 | match r { 60 | Ok(size) => { 61 | buffer.resize(size, 0); 62 | let rx = RxToken { 63 | //lower: Rc::new(RefCell::new(self.clone())), 64 | buffer, 65 | //size 66 | }; 67 | let tx = TxToken { 68 | lower: Rc::new(RefCell::new(self.clone())), 69 | }; 70 | Some((rx, tx)) 71 | }, 72 | //Simulates a tun/tap device that returns EWOULDBLOCK 73 | Err(VirtualTunReadError::WouldBlock) => None, 74 | } 75 | } 76 | 77 | fn transmit(&'d mut self) -> Option { 78 | Some(TxToken { 79 | lower: Rc::new(RefCell::new(self.clone())), 80 | }) 81 | } 82 | } 83 | 84 | #[doc(hidden)] 85 | pub struct RxToken { 86 | //lower: Rc>, 87 | buffer: Vec, 88 | //size: usize 89 | } 90 | 91 | impl phy::RxToken for RxToken { 92 | fn consume(mut self, _timestamp: Instant, f: F) -> smoltcp::Result 93 | where 94 | F: FnOnce(&mut [u8]) -> smoltcp::Result, 95 | { 96 | //let lower = self.lower.as_ref().borrow_mut(); 97 | f(self.buffer.as_mut_slice()) 98 | //r 99 | } 100 | } 101 | 102 | //https://stackoverflow.com/a/66579120/5884503 103 | //https://users.rust-lang.org/t/storing-the-return-value-from-an-fn-closure/57386/3?u=guerlando 104 | trait CallOnceSafe { 105 | fn call_once_safe(&mut self, x: &mut [u8]) -> smoltcp::Result; 106 | } 107 | 108 | impl smoltcp::Result> CallOnceSafe for Option { 109 | fn call_once_safe(&mut self, x: &mut [u8]) -> smoltcp::Result { 110 | // panics if called more than once - but A::consume() calls it only once 111 | let func = self.take().unwrap(); 112 | func(x) 113 | } 114 | } 115 | 116 | #[doc(hidden)] 117 | pub struct TxToken { 118 | lower: Rc>, 119 | } 120 | 121 | impl<'a> phy::TxToken for TxToken { 122 | fn consume(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result 123 | where 124 | F: FnOnce(&mut [u8]) -> smoltcp::Result, 125 | { 126 | let lower = self.lower.as_ref().borrow_mut(); 127 | let mut r: Option> = None; 128 | let mut f = Some(f); 129 | 130 | match (lower.on_virtual_tun_write)(&mut |x| { 131 | r = Some(f.call_once_safe(x)) 132 | }, len) { 133 | Ok(_size) => { 134 | //println!("WROTE {} BYTES TO VIRTUAL TUN", size); 135 | }, 136 | Err(_) => { 137 | panic!("virtual tun receive unknown error"); 138 | } 139 | } 140 | r.unwrap() 141 | } 142 | } 143 | --------------------------------------------------------------------------------