first commit

This commit is contained in:
Lucas Schumacher 2025-07-16 22:48:16 -04:00
commit 26bbb6e55c
7 changed files with 1992 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

841
Cargo.lock generated Normal file
View File

@ -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",
]

15
Cargo.toml Normal file
View File

@ -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"] }

687
src/arp.rs Normal file
View File

@ -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<u8>,
spa: Vec<u8>,
tha: Vec<u8>,
tpa: Vec<u8>,
}
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<Self> {
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<u8> {
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<u8> = 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<u8> {
let mut v: Vec<u8> = 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<u8> {
let mut v: Vec<u8> = 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<Ax25Frame> {
let sha = &self.sha[..].try_into()?;
let ax_dest = Address::from_octets(sha)?;
let arp: Vec<u8> = 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<u8>) -> 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<u8>,
ts: Instant,
}
impl PacketPending {
fn new(packet: Vec<u8>) -> PacketPending {
PacketPending {
packet,
ts: Instant::now(),
}
}
}
#[derive(Debug)]
pub enum ArpState {
Found(Address, Instant),
Pending(VecDeque<PacketPending>, 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<Mutex<HashMap<Ipv4Addr, ArpState>>>;
async fn update(
this: &ArpTable,
ip: &Ipv4Addr,
ax: &Address,
tnc: &Sender<Ax25Frame>,
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<TransportSlice>) {
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<Mutex<HashMap<Ipv4Addr, ArpState>>>,
tnc: Sender<Ax25Frame>,
mut ip_in_rx: Receiver<Vec<u8>>,
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Option<Ipv4Addr>,
) -> 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<ArpTable>,
//tnc: Tnc,
ip_in: Sender<Vec<u8>>,
ip_out: Receiver<Vec<u8>>,
}
impl AxIpBridge {
pub fn new(
ax_addr: Address,
tnc: (Receiver<Ax25Frame>, Sender<Ax25Frame>),
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Option<Ipv4Addr>,
) -> Self {
let (ip_in, ip_in_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = mpsc::channel(128);
let (ip_out_tx, ip_out) = mpsc::channel(128);
let arp_table: Arc<Mutex<HashMap<Ipv4Addr, ArpState>>> =
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<Vec<u8>>, Receiver<Vec<u8>>) {
(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<Mutex<HashMap<Ipv4Addr, ArpState>>>, //ArpTable,
// tnc: Tnc,
ip_out_tx: Sender<Vec<u8>>,
ip: Ipv4Addr,
mut tnc_rx: Receiver<Ax25Frame>,
tnc_tx: Sender<Ax25Frame>,
) -> 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(())
}

99
src/kiss_tokio.rs Normal file
View File

@ -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<Ax25Frame>,
to_tnc: Sender<Ax25Frame>,
}
impl KissTnc {
const FEND: u8 = 0xC0;
const FESC: u8 = 0xDB;
const TFEND: u8 = 0xDC;
const TFESC: u8 = 0xDD;
pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<KissTnc> {
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<Ax25Frame>, Sender<Ax25Frame>) {
(self.from_tnc, self.to_tnc)
}
async fn rx_thread(mut tcp_in: OwnedReadHalf, ax_out: Sender<Ax25Frame>) {
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<Ax25Frame>) {
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!();
}
}

87
src/main.rs Normal file
View File

@ -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<Ipv4Addr>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let ip_addr = cli.ip;
let netmask = cli.netmask;
let gateway: Option<Ipv4Addr> = 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(())
}

262
src/tools.rs Normal file
View File

@ -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<Address>;
}
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<Self> {
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<u8> {
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<u8>;
}
impl ToBeBytes for String {
fn to_be_bytes(&self) -> Vec<u8> {
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::<u8>::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<u8> {
vec![if *self { 1 } else { 0 }]
}
}
pub trait PoppableParser {
fn pop_u8(&mut self) -> anyhow::Result<u8>;
fn pop_u16(&mut self) -> anyhow::Result<u16>;
fn pop_u32(&mut self) -> anyhow::Result<u32>;
fn pop_u64(&mut self) -> anyhow::Result<u64>;
fn pop_i8(&mut self) -> anyhow::Result<i8>;
fn pop_i16(&mut self) -> anyhow::Result<i16>;
fn pop_i32(&mut self) -> anyhow::Result<i32>;
fn pop_i64(&mut self) -> anyhow::Result<i64>;
fn pop_f32(&mut self) -> anyhow::Result<f32>;
fn pop_f64(&mut self) -> anyhow::Result<f64>;
fn pop_bool(&mut self) -> anyhow::Result<bool>;
fn pop_string(&mut self) -> anyhow::Result<String>;
fn pop_bytes(&mut self, n_bytes: usize) -> anyhow::Result<Vec<u8>>;
}
impl PoppableParser for &[u8] {
fn pop_bytes(&mut self, n_bytes: usize) -> anyhow::Result<Vec<u8>> {
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<String> {
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<bool> {
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<i8> {
const SIZE: usize = std::mem::size_of::<i8>();
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<i16> {
const SIZE: usize = std::mem::size_of::<i16>();
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<i32> {
const SIZE: usize = std::mem::size_of::<i32>();
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<i64> {
const SIZE: usize = std::mem::size_of::<i64>();
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<u32> {
const SIZE: usize = std::mem::size_of::<u32>();
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<u64> {
const SIZE: usize = std::mem::size_of::<u64>();
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<u8> {
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<u16> {
const SIZE: usize = std::mem::size_of::<u16>();
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<f32> {
const SIZE: usize = std::mem::size_of::<f32>();
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<f64> {
const SIZE: usize = std::mem::size_of::<f64>();
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);
}