commit 26bbb6e55cdcbeeadd6837bfdb65a908677a06a9 Author: Lucas Schumacher Date: Wed Jul 16 22:48:16 2025 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2dbfc77 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,841 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "ax25" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fec6189a57a9668a17aa3368d110b1451450df47b85200f43d93357fb21f3b" + +[[package]] +name = "axtun" +version = "0.1.0" +dependencies = [ + "anyhow", + "ax25", + "clap", + "etherparse", + "futures", + "tokio", + "tun", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "c2rust-bitfields" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41" +dependencies = [ + "c2rust-bitfields-derive", +] + +[[package]] +name = "c2rust-bitfields-derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "etherparse" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "ioctl-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tun" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adb9992bbd5ca76f3847ed579ad4ee8defb2ec2eea918cceef17ccc66fa4fd4" +dependencies = [ + "byteorder", + "bytes", + "futures-core", + "ioctl-sys", + "libc", + "log", + "thiserror", + "tokio", + "tokio-util", + "wintun", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "wintun" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4" +dependencies = [ + "c2rust-bitfields", + "libloading", + "log", + "thiserror", + "windows", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..539e7ad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axtun" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.78" +ax25 = "0.3.0" +clap = { version = "4.4.13", features = ["derive"] } +etherparse = "0.13.0" +futures = "0.3.30" +tokio = { version = "1.35.1", features = ["full"] } +tun = { version = "0.6.1", features = ["tokio", "async"] } diff --git a/src/arp.rs b/src/arp.rs new file mode 100644 index 0000000..1dd92fb --- /dev/null +++ b/src/arp.rs @@ -0,0 +1,687 @@ +use crate::tools::AxAddrOctets; +use crate::tools::Mask; +use crate::tools::PoppableParser; + +use anyhow::{anyhow, Result}; + +use ax25::frame::Address; +use ax25::frame::Ax25Frame; +use ax25::frame::FrameContent; +use ax25::frame::ProtocolIdentifier; +use ax25::frame::UnnumberedInformation; + +use etherparse::InternetSlice; +use etherparse::Ipv4HeaderSlice; +use etherparse::SlicedPacket; +use etherparse::TransportSlice; + +use std::collections::HashMap; +use std::collections::VecDeque; +use std::net::Ipv4Addr; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio::sync::Mutex; + +const MAX_BUFFERED_PKTS: usize = 6; +const ARP_PENDING_TTL: Duration = Duration::from_secs(60); +const ARP_CACHE_TTL: Duration = Duration::from_secs(120); + +pub struct ArpPacket { + htype: u16, + ptype: u16, + hlen: u8, + plen: u8, + oper: u16, + sha: Vec, + spa: Vec, + tha: Vec, + tpa: Vec, +} + +impl ArpPacket { + /// ARP htype for AX.25 + const AX_HTYPE: [u8; 2] = [0, 3]; + /// AX.25 Protocol Identifier for arpa ip + const AX_ARPA_IP: [u8; 2] = [0, 0xCC]; + /// AX.25 address length in octets + const AX_HLEN: u8 = 7; + /// IPv4 address length in octets + const IP_PLEN: u8 = 4; + + pub fn from_bytes(mut bytes: &[u8]) -> Result { + let htype = bytes.pop_u16()?; + let ptype = bytes.pop_u16()?; + let hlen = bytes.pop_u8()?; + let plen = bytes.pop_u8()?; + Ok(Self { + htype, + ptype, + hlen, + plen, + oper: bytes.pop_u16()?, + sha: bytes.pop_bytes(hlen as usize)?, + spa: bytes.pop_bytes(plen as usize)?, + tha: bytes.pop_bytes(hlen as usize)?, + tpa: bytes.pop_bytes(plen as usize)?, + }) + } + pub fn ax_request_raw(ax_src: &Address, ip_src: Ipv4Addr, ip_dest: Ipv4Addr) -> Vec { + let htype = 0x03; // ARP htype for AX25 + let ptype = 0xCC; // AX25 protocolIdentifier for IP + let hlen = 7; // Ax25 address length in octets + let plen = 4; // IPv4 address length in octets + let mut v: Vec = vec![0, htype, 0, ptype, hlen, plen, 0, 1]; + + v.append(&mut ax_src.octets().to_vec()); + v.append(&mut ip_src.octets().to_vec()); + + v.append(&mut vec![0_u8; hlen as usize]); + v.append(&mut ip_dest.octets().to_vec()); + v + } + /*fn ax25_raw( + ax_src: &Address, + ip_src: &Ipv4Addr, + ax_dest: &Address, + ip_dest: &Ipv4Addr, + oper: u16, + ) -> Vec { + let mut v: Vec = vec![ + Self::AX_HTYPE[0], + Self::AX_HTYPE[1], + Self::AX_ARPA_IP[0], + Self::AX_ARPA_IP[1], + Self::AX_HLEN, + Self::IP_PLEN, + ((oper >> 8) & 0xFF) as u8, + (oper & 0xFF) as u8, + ]; + + v.append(&mut ax_src.octets().to_vec()); + v.append(&mut ip_src.octets().to_vec()); + + v.append(&mut ax_dest.octets().to_vec()); + v.append(&mut ip_dest.octets().to_vec()); + v + }*/ + fn ax25_zip( + ax_src: &[u8; Self::AX_HLEN as usize], + ip_src: &[u8; Self::IP_PLEN as usize], + ax_dest: &[u8; Self::AX_HLEN as usize], + ip_dest: &[u8; Self::IP_PLEN as usize], + oper: u16, + ) -> Vec { + let mut v: Vec = vec![ + Self::AX_HTYPE[0], + Self::AX_HTYPE[1], + Self::AX_ARPA_IP[0], + Self::AX_ARPA_IP[1], + Self::AX_HLEN, + Self::IP_PLEN, + ((oper >> 8) & 0xFF) as u8, + (oper & 0xFF) as u8, + ]; + + v.append(&mut ax_src.to_vec()); + v.append(&mut ip_src.to_vec()); + + v.append(&mut ax_dest.to_vec()); + v.append(&mut ip_dest.to_vec()); + v + } + pub fn ax_make_resp(&self, ax: Address) -> Result { + let sha = &self.sha[..].try_into()?; + let ax_dest = Address::from_octets(sha)?; + let arp: Vec = Self::ax25_zip( + &ax.octets(), + &self.tpa[..].try_into()?, + sha, + &self.spa[..].try_into()?, + 2, + ); + let frame_content = FrameContent::UnnumberedInformation(UnnumberedInformation { + pid: ProtocolIdentifier::ArpaAddress, + info: arp, + poll_or_final: false, + }); + Ok(Ax25Frame { + source: ax, + destination: ax_dest, + route: vec![], + command_or_response: None, + content: frame_content, + }) + } +} +fn qst_addr() -> Address { + let mut addr = Address::default(); + addr.callsign = "QST".to_string(); + addr +} +//fn ax_arp_resp() +fn ax_arp(ax_src: Address, ip_src: Ipv4Addr, ip_dest: Ipv4Addr) -> Ax25Frame { + let ax_dest = qst_addr(); + let frame_content = FrameContent::UnnumberedInformation(UnnumberedInformation { + pid: ax25::frame::ProtocolIdentifier::ArpaAddress, + info: ArpPacket::ax_request_raw(&ax_src, ip_src, ip_dest), + poll_or_final: false, + }); + let frame = Ax25Frame { + source: ax_src, + destination: ax_dest, + route: vec![], + command_or_response: None, + content: frame_content, + }; + frame +} +fn ax_ip(ax_src: Address, ax_dest: Address, ipdata: Vec) -> Ax25Frame { + let frame_content = FrameContent::UnnumberedInformation(UnnumberedInformation { + pid: ProtocolIdentifier::ArpaIp, + info: ipdata, + poll_or_final: false, + }); + Ax25Frame { + source: ax_src, + destination: ax_dest, + route: vec![], + command_or_response: None, + content: frame_content, + } +} + +#[derive(Debug)] +pub struct PacketPending { + packet: Vec, + ts: Instant, +} +impl PacketPending { + fn new(packet: Vec) -> PacketPending { + PacketPending { + packet, + ts: Instant::now(), + } + } +} +#[derive(Debug)] +pub enum ArpState { + Found(Address, Instant), + Pending(VecDeque, Instant), + PendingUpdate(Address, Instant), +} +fn try_print_ip(tx: bool, p: &[u8]) -> Result<()> { + let packet = SlicedPacket::from_ip(p)?; + if let Some(InternetSlice::Ipv4(h, _)) = packet.ip { + print_ip(tx, &h, &packet.transport); + Ok(()) + } else { + Err(anyhow!("Error not IPv4")) + } +} + +type ArpTable = Arc>>; +async fn update( + this: &ArpTable, + ip: &Ipv4Addr, + ax: &Address, + tnc: &Sender, + ax_src: &Address, +) -> Result<()> { + println!("\t\tARP Updating Table: {} is {}", ip, ax); + let now = Instant::now(); + let mut tbl = this.lock().await; + //match tbl.get_mut(&ip) { + let old = tbl.insert(ip.clone(), ArpState::Found(ax.clone(), now)); + drop(tbl); + //println!("\t\told: {:?}", old); + + if let Some(ArpState::Pending(pending, _)) = old { + //println!("\t\tYES! YES! YES! YES! YES!"); + for p in pending { + //println!("Packet"); + if p.ts.elapsed() < ARP_PENDING_TTL { + // tnc.send_frame(&ax_ip(ax_src.clone(), ax.clone(), p.packet))?; + if try_print_ip(true, &p.packet).is_err() { + println!("TX Pending Frame"); + } + tnc.send(ax_ip(ax_src.clone(), ax.clone(), p.packet)) + .await?; + } + } + } + Ok(()) +} +fn print_ip(tx: bool, ip_h: &Ipv4HeaderSlice, ts_h: &Option) { + let tx = if tx { "TX" } else { "RX" }; + let dst_ip = ip_h.destination_addr(); + let src_ip = ip_h.source_addr(); + match ts_h { + Some(TransportSlice::Tcp(h)) => { + let src_prt = h.source_port(); + let dst_prt = h.destination_port(); + let o = h.sequence_number(); + if h.ack() { + println!( + "{tx} Frame: TCPACK {}:{} -> {}:{} ({o})", + src_ip, src_prt, dst_ip, dst_prt + ); + } else { + println!( + "{tx} Frame: TCP/IP {}:{} -> {}:{} ({o})", + src_ip, src_prt, dst_ip, dst_prt + ); + } + } + Some(TransportSlice::Udp(h)) => { + let src_prt = h.source_port(); + let dst_prt = h.destination_port(); + println!( + "{tx} Frame: UDP/IP {}:{} -> {}:{}", + src_ip, src_prt, dst_ip, dst_prt + ); + } + _ => { + println!("{tx} Frame: IP {} -> {}", src_ip, dst_ip); + } + } +} +/*fn print_ip(tx: bool, packet: &SlicedPacket) { + let tx = if tx { "TX" } else { "RX" }; + let (dst_ip, src_ip)if let Some(InternetSlice::Ipv4(header, _)) = packet.ip { + let dst_ip = header.destination_addr(); + let src_ip = header.source_addr(); + match packet.transport { + Some(TransportSlice::Tcp(h)) => { + let src_prt = h.source_port(); + let dst_prt = h.destination_port(); + let o = h.sequence_number(); + if h.ack() { + println!( + "RX Frame: TCPACK {}:{} -> {}:{} ({o})", + src_ip, src_prt, dst_ip, dst_prt + ); + } else { + println!( + "RX Frame: TCP/IP {}:{} -> {}:{} ({o})", + src_ip, src_prt, dst_ip, dst_prt + ); + } + } + Some(TransportSlice::Udp(h)) => { + let src_prt = h.source_port(); + let dst_prt = h.destination_port(); + println!( + "RX Frame: UDP/IP {}:{} -> {}:{}", + src_ip, src_prt, dst_ip, dst_prt + ); + } + _ => { + println!("RX Frame: IP {} -> {}", src_ip, dst_ip); + } + } + } +}*/ + +/// Thread that accepts incoming IP packets and sends AX.25 packets +async fn process_ip( + ax_addr: Address, + arp_table: Arc>>, + tnc: Sender, + mut ip_in_rx: Receiver>, + ip_addr: Ipv4Addr, + netmask: Ipv4Addr, + gateway: Option, +) -> anyhow::Result<()> { + let allow_multicast = false; + let allow_broadcast = false; + let local_broadcast = Ipv4Addr::from(ip_addr.mask(&netmask) | !u32::from(netmask)); + // let local_broadcast = Ipv4Addr::from((u32::from(ip) & u32::from(subnetmask)) | !u32::from(subnetmask)); + //while let Some(packet) = ip_in_rx.recv().await { + loop { + let packet = ip_in_rx + .recv() + .await + .ok_or(anyhow!("IP input buffer closed"))?; + + match SlicedPacket::from_ip(&packet) { + Err(value) => println!("Err {:?}", value), + Ok(value) => { + match value.ip { + Some(InternetSlice::Ipv4(header, _exts)) => { + let packet_dest_ip = header.destination_addr(); + if packet_dest_ip.is_multicast() { + if allow_multicast { + let ax_dest = qst_addr(); + tnc.send(ax_ip(ax_addr.clone(), ax_dest, packet)).await?; + } + continue; + } + if packet_dest_ip.is_broadcast() || packet_dest_ip == local_broadcast { + if allow_broadcast { + let ax_dest = qst_addr(); + tnc.send(ax_ip(ax_addr.clone(), ax_dest, packet)).await?; + } + continue; + } + + let same_subnet = packet_dest_ip.mask(&netmask) == ip_addr.mask(&netmask); + let frame_dest_ip = match (same_subnet, gateway) { + (true, _) => packet_dest_ip, + (false, Some(gateway_ip)) => gateway_ip, + (false, None) => continue, + }; + + let src_ip = header.source_addr(); + let mut tbl = arp_table.lock().await; + match tbl.get_mut(&frame_dest_ip) { + Some(state) => { + match state { + ArpState::Found(dest_ax, _ts) => { + let dest_ax = dest_ax.clone(); + //tnc.send_frame(&ax_ip(ax_addr.clone(), dest_ax.clone(), packet))?; + print_ip(true, &header, &value.transport); + tnc.send(ax_ip(ax_addr.clone(), dest_ax.clone(), packet)) + .await?; + if _ts.elapsed() > ARP_CACHE_TTL { + println!( + "TX Frame: ARP Who has {}? Tell {}", + frame_dest_ip, src_ip + ); + tnc.send(ax_arp( + ax_addr.clone(), + src_ip, + frame_dest_ip, + )) + .await?; + *state = ArpState::PendingUpdate( + dest_ax.clone(), + Instant::now(), + ); + println!( + "\tFound {} in table: {} EXPIRED!", + frame_dest_ip, dest_ax + ); + } else { + println!( + "\tFound {} in table: {}", + frame_dest_ip, dest_ax + ); + } + } + ArpState::Pending(packets, ts) => { + packets.push_back(PacketPending::new(packet)); + while packets.len() > MAX_BUFFERED_PKTS { + packets.pop_front(); + } + if ts.elapsed() > ARP_CACHE_TTL { + //tnc.send_frame(&ax_arp(ax_addr.clone(), src_ip, dest_ip))?; + println!( + "TX Frame: ARP Who has {}? Tell {}", + frame_dest_ip, src_ip + ); + tnc.send(ax_arp( + ax_addr.clone(), + src_ip, + frame_dest_ip, + )) + .await?; + println!( + "\t{} Not found: pending (sent req)", + frame_dest_ip + ); + } else { + println!("\t{} Not found: pending", frame_dest_ip); + } + } + ArpState::PendingUpdate(dest_ax, _ts) => { + let dest_ax = dest_ax.clone(); + print_ip(true, &header, &value.transport); + tnc.send(ax_ip(ax_addr.clone(), dest_ax.clone(), packet)) + .await?; + if _ts.elapsed() > ARP_CACHE_TTL { + println!( + "TX Frame: ARP Who has {}? Tell {}", + frame_dest_ip, src_ip + ); + tnc.send(ax_arp( + ax_addr.clone(), + src_ip, + frame_dest_ip, + )) + .await?; + *state = ArpState::PendingUpdate( + dest_ax.clone(), + Instant::now(), + ); + println!( + "\t{} Found: {} pending update (sent req)", + frame_dest_ip, dest_ax + ); + } else { + println!( + "\t{} Found: {} pending update", + frame_dest_ip, dest_ax + ); + } + } + } + } + None => { + tbl.insert( + frame_dest_ip, + ArpState::Pending( + VecDeque::from([PacketPending::new(packet)]), + Instant::now(), + ), + ); + //tnc.send_frame(&ax_arp(ax_addr.clone(), src_ip, dest_ip.clone()))?; + println!( + "TX Frame: ARP Who has {}? Tell {}", + frame_dest_ip, src_ip + ); + tnc.send(ax_arp(ax_addr.clone(), src_ip, frame_dest_ip.clone())) + .await?; + println!("\t{} Not found (sent req)", frame_dest_ip); + } + } + } + _ => {} //Silently drop non Ipv4 packets + } + } + } + } +} + +#[test] +pub fn test() { + use std::str::FromStr; + assert!(Ipv4Addr::from_str("224.0.0.251").unwrap().is_multicast()); + assert!(Ipv4Addr::from_str("239.255.255.250") + .unwrap() + .is_multicast()); + assert!(Ipv4Addr::from_str("224.0.0.22").unwrap().is_multicast()); +} + +pub struct AxIpBridge { + //addr: Address, + //arp_table: Arc, + //tnc: Tnc, + ip_in: Sender>, + ip_out: Receiver>, +} +impl AxIpBridge { + pub fn new( + ax_addr: Address, + tnc: (Receiver, Sender), + ip_addr: Ipv4Addr, + netmask: Ipv4Addr, + gateway: Option, + ) -> Self { + let (ip_in, ip_in_rx): (Sender>, Receiver>) = mpsc::channel(128); + let (ip_out_tx, ip_out) = mpsc::channel(128); + let arp_table: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + let (tnc_rx, tnc_tx) = tnc; + + // Thread that accepts incoming IP packets and sends AX.25 packets + tokio::spawn(process_ip( + ax_addr.clone(), + arp_table.clone(), + tnc_tx.clone(), + ip_in_rx, + ip_addr.clone(), + netmask, + gateway, + )); + + // Thread that accepts incoming AX.25 packets and sends IP packets + tokio::spawn(process_ax( + ax_addr, arp_table, ip_out_tx, ip_addr, tnc_rx, tnc_tx, + )); + + Self { ip_out, ip_in } + } + pub fn split(self) -> (Sender>, Receiver>) { + (self.ip_in, self.ip_out) + } +} +/// Thread that accepts incoming AX.25 packets and sends IP packets +async fn process_ax( + ax_addr: Address, + arp_table: Arc>>, //ArpTable, + // tnc: Tnc, + ip_out_tx: Sender>, + ip: Ipv4Addr, + mut tnc_rx: Receiver, + tnc_tx: Sender, +) -> Result<()> { + //let ax_in = tnc.incoming(); + + //while let Ok(frame) = ax_in.recv().unwrap() { + loop { + //let frame = ax_in.recv()??; + let frame = tnc_rx.recv().await.ok_or(anyhow!("Error TNC closed"))?; + //println!("Got frame from TNC"); + if let FrameContent::UnnumberedInformation(content) = frame.content { + //println!("Frame is UnnumberedInformation"); + match content.pid { + ProtocolIdentifier::ArpaIp => { + // Content is IP packet + if let Ok(packet) = SlicedPacket::from_ip(&content.info) { + // Drop IPv6 for now + if let Some(InternetSlice::Ipv4(header, _)) = packet.ip { + print_ip(false, &header, &packet.transport); + let dst_ip = header.destination_addr(); + + if frame.destination == ax_addr && dst_ip == ip { + //TODO CHECK THIS SHIT + //TODO don't update table for self (and maybe just drop the packet) + //update(&arp_table, &ip, &frame.source, &tnc, &ax_addr)?; + update( + &arp_table, + &header.source_addr(), + &frame.source, + &tnc_tx, + &ax_addr, + ) + .await?; + } + ip_out_tx.send(content.info).await?; + } else if let Some(TransportSlice::Icmpv4(h)) = packet.transport { + // I don't think this will ever happen with a tun interface + // all tun Icmpv4 packets should get caught by the previous if statement + println!("{:?}", h.icmp_type()); + ip_out_tx.send(content.info).await?; + } + } + } + ProtocolIdentifier::ArpaAddress => { + // Content is ARP packet + //if let Ok(packet) = ArpPacket::from_bytes(&content.info) { + match ArpPacket::from_bytes(&content.info) { + Err(e) => { + eprintln!("Error parsing ARP packet: {e}") + } + Ok(packet) => { + //println!("Frame is ARP"); + if packet.htype != 0x0003 + || packet.ptype != 0x00CC + || packet.hlen != 7 + || packet.plen != 4 + { + continue; + } + if packet.oper == 1 { + // Request! + let target_ip = Ipv4Addr::new( + packet.tpa[0], + packet.tpa[1], + packet.tpa[2], + packet.tpa[3], + ); + let sender_ip = Ipv4Addr::new( + packet.spa[0], + packet.spa[1], + packet.spa[2], + packet.spa[3], + ); + println!("RX Frame: ARP Who has {}? Tell {}", target_ip, sender_ip); + if ip == target_ip { + println!("TX Frame: ARP {} is at {}", target_ip, ax_addr); + let resp = packet.ax_make_resp(ax_addr.clone())?; + tnc_tx.send(resp).await?; + } + if let Ok(sender_ax) = + Address::from_octets(packet.sha[..].try_into()?) + { + update(&arp_table, &sender_ip, &sender_ax, &tnc_tx, &ax_addr) + .await?; + } + } else if packet.oper == 2 { + //response! + //update(senderip, sendercall) + let sender_ip = Ipv4Addr::new( + packet.spa[0], + packet.spa[1], + packet.spa[2], + packet.spa[3], + ); + let target_ip = Ipv4Addr::new( + packet.tpa[0], + packet.tpa[1], + packet.tpa[2], + packet.tpa[3], + ); + if let Ok(sender_ax) = + Address::from_octets(packet.sha[..].try_into()?) + { + println!("RX Frame: ARP {} is at {}", sender_ip, sender_ax); + update(&arp_table, &sender_ip, &sender_ax, &tnc_tx, &ax_addr) + .await?; + } else { + println!("RX Frame: ARP Could not decode sender AX Call"); + } + + if ip != target_ip { + if let Ok(target_ax) = + Address::from_octets(packet.tha[..].try_into()?) + { + update( + &arp_table, &sender_ip, &target_ax, &tnc_tx, &ax_addr, + ) + .await?; + } + // update(target) + } + } + } + } + } + _ => {} // drop frame Silently + } + } + } + //Ok(()) +} diff --git a/src/kiss_tokio.rs b/src/kiss_tokio.rs new file mode 100644 index 0000000..ce3b88d --- /dev/null +++ b/src/kiss_tokio.rs @@ -0,0 +1,99 @@ +use anyhow::Result; +use ax25::frame::Ax25Frame; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{ + tcp::{OwnedReadHalf, OwnedWriteHalf}, + TcpStream, ToSocketAddrs, + }, + sync::mpsc::{self, Receiver, Sender}, +}; + +/// Kiss TNC using the Tokio Async runtime +pub struct KissTnc { + from_tnc: Receiver, + to_tnc: Sender, +} +impl KissTnc { + const FEND: u8 = 0xC0; + const FESC: u8 = 0xDB; + const TFEND: u8 = 0xDC; + const TFESC: u8 = 0xDD; + pub async fn connect(addr: A) -> Result { + let socket = TcpStream::connect(addr).await?; + let (tcp_rx, tcp_tx) = socket.into_split(); + + let (ax_tx, from_tnc) = mpsc::channel(128); + let (to_tnc, ax_rx) = mpsc::channel(128); + + tokio::spawn(Self::rx_thread(tcp_rx, ax_tx)); + tokio::spawn(Self::tx_thread(tcp_tx, ax_rx)); + + Ok(KissTnc { from_tnc, to_tnc }) + } + pub fn split(self) -> (Receiver, Sender) { + (self.from_tnc, self.to_tnc) + } + async fn rx_thread(mut tcp_in: OwnedReadHalf, ax_out: Sender) { + let mut buf = [0_u8; 2048]; + let mut frame = vec![]; + let mut escaped = false; + while let Ok(n) = tcp_in.read(&mut buf).await { + if n == 0 { + break; + } + for byte in buf[..n].iter() { + if escaped { + match *byte { + 0xDC => frame.push(0xC0), + 0xDD => frame.push(0xDB), + _ => {} + } + escaped = false; + } else { + match *byte { + 0xDB => escaped = true, + 0xC0 => { + if frame.len() == 0 { + continue; + } + if let Ok(ax) = Ax25Frame::from_bytes(&frame[1..]) { + //println!("KISS RX FRAME"); + if ax_out.send(ax).await.is_err() { + break; + } + } + frame.clear(); + } + b => frame.push(b), + } + } + } + } + } + async fn tx_thread(mut tcp_out: OwnedWriteHalf, mut ax_in: Receiver) { + while let Some(ax_frame) = ax_in.recv().await { + let mut kiss_frame = vec![Self::FEND, 0x00]; + for byte in ax_frame.to_bytes() { + match byte { + Self::FEND => { + kiss_frame.push(Self::FESC); + kiss_frame.push(Self::TFEND); + } + Self::FESC => { + kiss_frame.push(Self::FESC); + kiss_frame.push(Self::TFESC); + } + byte => kiss_frame.push(byte), + } + } + kiss_frame.push(Self::FEND); + if tcp_out.write(&kiss_frame).await.is_err() { + break; + } else { + //println!("KISS TX FRAME"); + } + } + todo!(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0f49cf3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,87 @@ +use clap::Parser; +use futures::{SinkExt, StreamExt}; +use std::net::Ipv4Addr; +use std::str::FromStr; +use tun::TunPacket; + +use ax25::frame::Address; + +mod arp; +mod tools; + +mod kiss_tokio; +use kiss_tokio::KissTnc; + +#[derive(Parser)] +struct Cli { + call: String, + kiss: String, + ip: Ipv4Addr, + netmask: Ipv4Addr, + gateway: Option, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let ip_addr = cli.ip; + let netmask = cli.netmask; + let gateway: Option = cli.gateway; + let ax_addr = Address::from_str(&cli.call).unwrap(); + + let tnc = KissTnc::connect(&cli.kiss).await?; + + let mut config = tun::Configuration::default(); + config.address(ip_addr).netmask(netmask).up(); + + #[cfg(target_os = "linux")] + config.platform(|config| { + config.packet_information(false); + }); + + let (mut dev_sink, mut dev_source) = tun::create_as_async(&config)?.into_framed().split(); + + let arp_bridge = arp::AxIpBridge::new(ax_addr, tnc.split(), ip_addr, netmask, gateway); + let (to_bridge, mut from_bridge) = arp_bridge.split(); + + // read packets from the bridge and send them to the os tun device + tokio::spawn(async move { + loop { + match from_bridge.recv().await { + Some(pkt) => { + //println!("RX PKT"); + if let Err(e) = dev_sink.send(TunPacket::new(pkt)).await { + println!("Got error {e}"); + break; + } + } + None => { + println!("Error: Channel Closed!"); + break; + } + } + } + }); + + // read packets from the os using the tun device source + loop { + match dev_source.next().await { + Some(Ok(pkt)) => { + //println!("TX PKT"); + if let Err(e) = to_bridge.send(pkt.get_bytes().to_vec()).await { + println!("Got error {e}"); + break; + } + } + Some(Err(e)) => { + println!("Got err: {e}"); + } + None => { + println!("Error: Stream Closed!"); + break; + } + } + } + Ok(()) +} diff --git a/src/tools.rs b/src/tools.rs new file mode 100644 index 0000000..7a4aa28 --- /dev/null +++ b/src/tools.rs @@ -0,0 +1,262 @@ +use anyhow::bail; +use std::str; + +// TODO (maybe) impl trait ToBits for Ipv4Addr then imple trait MAsk for T where T: ToBits +// Then throw all of that out when #![feature(ip_bits)] gets out of nightly +pub trait Mask { + fn mask(self, mask: &Self) -> u32; + fn to_bits(self) -> u32; +} +impl Mask for std::net::Ipv4Addr { + fn mask(self, mask: &Self) -> u32 { + u32::from_be_bytes(self.octets()) & u32::from_be_bytes(mask.octets()) + } + fn to_bits(self) -> u32 { + u32::from_be_bytes(self.octets()) + } +} + +use ax25::frame::Address; +pub trait AxAddrOctets { + fn octets(&self) -> [u8; 7]; + fn from_octets(octets: &[u8; 7]) -> anyhow::Result
; +} +impl AxAddrOctets for Address { + fn octets(&self) -> [u8; 7] { + let mut octets = [0_u8; 7]; + let ascii = self.callsign.as_bytes(); + for i in 0..6 { + let byte = match ascii.get(i) { + Some(c) => c << 1, + None => b' ' << 1, + }; + octets[i] = byte; + } + octets[6] = self.ssid << 1; + octets + } + fn from_octets(octets: &[u8; 7]) -> anyhow::Result { + let mut addr = Address::default(); + let ascii = octets[..6].iter().map(|x| x >> 1).collect(); + addr.callsign = String::from_utf8(ascii)?; + addr.ssid = octets[6] >> 1; + Ok(addr) + } +} +impl ToBeBytes for Address { + fn to_be_bytes(&self) -> Vec { + let mut v = vec![]; + for byte in self.callsign.as_bytes() { + v.push(*byte); + } + v.push(self.ssid); + v + } +} + +pub trait ToBeBytes { + fn to_be_bytes(&self) -> Vec; +} +impl ToBeBytes for String { + fn to_be_bytes(&self) -> Vec { + let mut s = self.clone().into_bytes(); + let n = s.len() as u32; + if n == 0 { + return vec![!0, !0, !0, !0]; // Return NULLSTR if empty + } + let mut v = Vec::::new(); + v.append(&mut n.to_be_bytes().to_vec()); + v.append(&mut s); + v + } +} +impl ToBeBytes for bool { + fn to_be_bytes(&self) -> Vec { + vec![if *self { 1 } else { 0 }] + } +} +pub trait PoppableParser { + fn pop_u8(&mut self) -> anyhow::Result; + fn pop_u16(&mut self) -> anyhow::Result; + fn pop_u32(&mut self) -> anyhow::Result; + fn pop_u64(&mut self) -> anyhow::Result; + fn pop_i8(&mut self) -> anyhow::Result; + fn pop_i16(&mut self) -> anyhow::Result; + fn pop_i32(&mut self) -> anyhow::Result; + fn pop_i64(&mut self) -> anyhow::Result; + fn pop_f32(&mut self) -> anyhow::Result; + fn pop_f64(&mut self) -> anyhow::Result; + fn pop_bool(&mut self) -> anyhow::Result; + fn pop_string(&mut self) -> anyhow::Result; + fn pop_bytes(&mut self, n_bytes: usize) -> anyhow::Result>; +} + +impl PoppableParser for &[u8] { + fn pop_bytes(&mut self, n_bytes: usize) -> anyhow::Result> { + if self.len() < n_bytes { + anyhow::bail!("Buffer too small to pop {} bytes", n_bytes); + } + let value = self[..n_bytes].to_vec(); + //let mut value = vec![0_u8; n_bytes]; + //value.copy_from_slice(&self[..n_bytes]); + *self = &self[n_bytes..]; + Ok(value) + } + fn pop_string(&mut self) -> anyhow::Result { + const NULLSTR: u32 = !0; + let z = self.len(); + let size: usize = match self.pop_u32()? { + NULLSTR => 0, + size => size as usize, + }; + assert_ne!(z, self.len()); + if self.len() < size { + anyhow::bail!("Buffer too small to parse String of length {}!", size); + } + let value = str::from_utf8(&self[..size])?; + // println!("\t\tGot value: string = {}, len = {}\t\tsize: {}", value, size, self.len()); + *self = &self[size..]; + Ok(value.to_owned()) + } + fn pop_bool(&mut self) -> anyhow::Result { + match self.pop_u8() { + Err(_) => bail!("Buffer too small to parse bool!\t\tsize: {}", self.len()), + Ok(i) => { + // println!("\t\tGot value: bool = {}\t\tsize: {}", i!=0, self.len()); + Ok(i != 0) + } + } + // const SIZE: usize = 1; + // if self.len() < SIZE { anyhow::bail!("Buffer too small to parse bool!"); } + // let value = self[0] != 0; + // *self = &self[SIZE..]; + // Ok(value) + // Ok(self.pop_u8()? != 0) + } + fn pop_i8(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer to small to parse i8!\t\tsize: {}", self.len()); + } + let value = self[0] as i8; + // println!("\t\tGot value: i8 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_i16(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer to small to parse i16!\t\tsize: {}", self.len()); + } + let value = i16::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: i8 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_i32(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer to small to parse i32!\t\tsize: {}", self.len()); + } + let value = i32::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: i32 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_i64(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer to small to parse i32!\t\tsize: {}", self.len()); + } + let value = i64::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: i64 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_u32(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer too small to parse u32!\t\tsize: {}", self.len()); + } + let value = u32::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: u32 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_u64(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer too small to parse u64!\t\tsize: {}", self.len()); + } + let value = u64::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: u64 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_u8(&mut self) -> anyhow::Result { + const SIZE: usize = 1; + if self.len() < SIZE { + anyhow::bail!("Buffer too small to parse u8!\t\tsize: {}", self.len()); + } + let value = self[0]; + // println!("\t\tGot value: u8 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_u16(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer to small to parse u16!\t\tsize: {}", self.len()); + } + let value = u16::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: i8 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_f32(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer too small to parse f32!\t\tsize: {}", self.len()); + } + let value = f32::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: f32 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } + fn pop_f64(&mut self) -> anyhow::Result { + const SIZE: usize = std::mem::size_of::(); + if self.len() < SIZE { + anyhow::bail!("Buffer too small to parse f64!\t\tsize: {}", self.len()); + } + let value = f64::from_be_bytes(self[..SIZE].try_into()?); + // println!("\t\tGot value: f64 = {}\t\tsize: {}", value, self.len()); + *self = &self[SIZE..]; + Ok(value) + } +} + +#[test] +fn test_popper() { + let mut buf: &[u8] = &[ + 0, //false + 1, //true + 0, 0, 0, 6, // 6: u32 be + 0, 0, 0, 9, // 9: u32 be + 255, // 255: u8 + 0, 0, 0, 1, 0, 0, 0, 0, // 1<<32: u64 + ]; + assert_eq!(false, buf.pop_bool().unwrap()); + assert_eq!(true, buf.pop_bool().unwrap()); + assert_eq!(6, buf.pop_u32().unwrap()); + assert_eq!(9, buf.pop_u32().unwrap()); + assert_eq!(255, buf.pop_u8().unwrap()); + assert_eq!(1 << 32, buf.pop_u64().unwrap()); +} + +#[test] +fn test_string_serialization() { + let og = String::from("Hello World"); + let by = og.to_be_bytes(); + let s = by.as_slice().pop_string().unwrap(); + assert_eq!(og, s); +}