implement auth, improve session stats
This commit is contained in:
1 parent
bdf5455a3f
commit
8962406c57
7 files changed
+1204
-80
No files matched your search
Generated
+210
-3
@@ -88,6 +88,15 @@ version = "2.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.2"
|
version = "1.25.2"
|
||||||
@@ -184,6 +193,21 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@@ -195,6 +219,15 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defmt"
|
name = "defmt"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -223,7 +256,18 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0"
|
checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"const-oid",
|
||||||
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -404,6 +448,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hybrid-array"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.2"
|
version = "2.14.2"
|
||||||
@@ -412,6 +465,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -429,6 +484,15 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -499,6 +563,16 @@ version = "0.4.34"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.5",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
@@ -576,6 +650,15 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
@@ -750,19 +833,25 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
|
"digest",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures 0.3.34",
|
"futures 0.3.34",
|
||||||
"humantime",
|
"humantime",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"itertools",
|
"itertools 0.12.1",
|
||||||
"log",
|
"log",
|
||||||
|
"md-5",
|
||||||
"nix",
|
"nix",
|
||||||
"nom",
|
"nom",
|
||||||
"nom-derive",
|
"nom-derive",
|
||||||
"proc-bitfield",
|
"proc-bitfield",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
|
"serde_valid",
|
||||||
"serde_yaml_ng",
|
"serde_yaml_ng",
|
||||||
|
"sha1",
|
||||||
|
"subtle",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-timer",
|
"tokio-timer",
|
||||||
]
|
]
|
||||||
@@ -795,6 +884,16 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_bytes"
|
||||||
|
version = "0.11.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.229"
|
version = "1.0.229"
|
||||||
@@ -815,6 +914,59 @@ dependencies = [
|
|||||||
"syn 3.0.9",
|
"syn 3.0.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.154"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_valid"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a812672d5d6c94c0fa540e62dc318728410c78f40d60ae6c07afc845616da5f6"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"num-traits",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_valid_derive",
|
||||||
|
"serde_valid_literal",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_valid_derive"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "539cd22aaf48cb9a8932c8b7f5d54f51719b2f826564f9da1a9902ee411d753c"
|
||||||
|
dependencies = [
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_valid_literal"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "433e602b939d2e8e8b47f30f919b27a984183974079de2262ca876c2162657c8"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml_ng"
|
name = "serde_yaml_ng"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -828,6 +980,17 @@ dependencies = [
|
|||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.5",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.8"
|
version = "1.4.8"
|
||||||
@@ -872,6 +1035,12 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
@@ -905,13 +1074,33 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.21"
|
version = "2.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -975,12 +1164,24 @@ dependencies = [
|
|||||||
"tokio-executor",
|
"tokio-executor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -1033,3 +1234,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -8,18 +8,24 @@ atomic = "0.6.0"
|
|||||||
bytemuck = { version = "1.14.1", features = ["derive"] }
|
bytemuck = { version = "1.14.1", features = ["derive"] }
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
digest = "0.11.3"
|
||||||
env_logger = "0.11.1"
|
env_logger = "0.11.1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
humantime = "2.4.0"
|
humantime = "2.4.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
itertools = "0.12.1"
|
itertools = "0.12.1"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
md-5 = "0.11.0"
|
||||||
nix = { version = "0.31.3", features = ["fs", "socket"] }
|
nix = { version = "0.31.3", features = ["fs", "socket"] }
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
nom-derive = "0.10.1"
|
nom-derive = "0.10.1"
|
||||||
proc-bitfield = "0.3.1"
|
proc-bitfield = "0.3.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
|
serde_bytes = "0.11.19"
|
||||||
|
serde_valid = "3.2.0"
|
||||||
serde_yaml_ng = "0.10.0"
|
serde_yaml_ng = "0.10.0"
|
||||||
|
sha1 = "0.11.0"
|
||||||
|
subtle = "2.6"
|
||||||
tokio = { version = "1.35.1", features = ["net", "full"] }
|
tokio = { version = "1.35.1", features = ["net", "full"] }
|
||||||
tokio-timer = "0.2.13"
|
tokio-timer = "0.2.13"
|
||||||
@@ -39,3 +39,34 @@ peers:
|
|||||||
# desired_min_tx: 1s
|
# desired_min_tx: 1s
|
||||||
# required_min_rx: 1s
|
# required_min_rx: 1s
|
||||||
# detect_mult: 5
|
# detect_mult: 5
|
||||||
|
#
|
||||||
|
# Authentication (RFC 5880 section 6.7), per peer and off by default. Both ends must be configured identically; a
|
||||||
|
# peer that does not authenticate as configured is discarded. `key_id` is a number (0-255) that must match the remote's.
|
||||||
|
# Keys are used verbatim (not hashed or derived), so they must be exactly the length shown. Give them as a string;
|
||||||
|
# use YAML double-quoted escapes (\x00, \x1b, ...) for bytes that are not printable. Strings are UTF-8, so only bytes
|
||||||
|
# 0x00-0x7f can be written this way. To use bytes 0x80-0xff, give the key as a list of integers instead, one per
|
||||||
|
# byte (hex is fine), with the same length requirement:
|
||||||
|
# key: [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x80, 0xff]
|
||||||
|
#
|
||||||
|
# Keyed MD5: `key` is exactly 16 bytes. `meticulous` (default true) selects Meticulous Keyed MD5, in which every
|
||||||
|
# packet must carry a newer sequence number; the remote must be set to the same mode.
|
||||||
|
# - address: 192.0.2.4
|
||||||
|
# auth:
|
||||||
|
# type: md5
|
||||||
|
# key_id: 1
|
||||||
|
# key: "0123456789abcdef"
|
||||||
|
# meticulous: true
|
||||||
|
#
|
||||||
|
# Keyed SHA1: `key` is exactly 20 bytes. `meticulous` as above.
|
||||||
|
# - address: 192.0.2.5
|
||||||
|
# auth:
|
||||||
|
# type: sha1
|
||||||
|
# key_id: 1
|
||||||
|
# key: "0123456789abcdef\x00\x01\x7f\x1b"
|
||||||
|
#
|
||||||
|
# Simple password: 1 to 16 bytes, sent in the clear. Provides no protection against an attacker on the path.
|
||||||
|
# - address: 192.0.2.6
|
||||||
|
# auth:
|
||||||
|
# type: password
|
||||||
|
# key_id: 1
|
||||||
|
# password: "secret"
|
||||||
+636
-47
@@ -1,16 +1,24 @@
|
|||||||
|
use digest::Digest;
|
||||||
|
use log::warn;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
io::Cursor,
|
io::{Cursor, Write},
|
||||||
ops::{Div, Mul},
|
ops::{Div, Mul},
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytemuck::NoUninit;
|
use bytemuck::NoUninit;
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
use nom::{bytes::complete::take, multi::many_m_n, number::complete::be_u8, IResult};
|
use nom::{
|
||||||
|
bytes::complete::take,
|
||||||
|
multi::many_m_n,
|
||||||
|
number::complete::{be_u32, be_u8},
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
use nom_derive::{NomBE, Parse};
|
use nom_derive::{NomBE, Parse};
|
||||||
use proc_bitfield::*;
|
use proc_bitfield::*;
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)]
|
#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)]
|
||||||
@@ -84,6 +92,21 @@ pub enum BfdAuthType {
|
|||||||
MetKeyedSHA1 = 5,
|
MetKeyedSHA1 = 5,
|
||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for BfdAuthType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::None => "none",
|
||||||
|
Self::SimplePassword => "password",
|
||||||
|
Self::KeyedMD5 => "md5",
|
||||||
|
Self::MetKeyedMD5 => "meticulous-md5",
|
||||||
|
Self::KeyedSHA1 => "sha1",
|
||||||
|
Self::MetKeyedSHA1 => "meticulous-sha1",
|
||||||
|
Self::Reserved => "reserved",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit, Hash)]
|
#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit, Hash)]
|
||||||
pub struct BfdDiscriminator(pub u32);
|
pub struct BfdDiscriminator(pub u32);
|
||||||
@@ -181,7 +204,7 @@ impl Div<BfdInterval> for u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(NomBE)]
|
#[derive(Clone, Copy, NomBE)]
|
||||||
pub struct BfdFlags(pub u32): Debug {
|
pub struct BfdFlags(pub u32): Debug {
|
||||||
pub vers: u8 @ 29..=31,
|
pub vers: u8 @ 29..=31,
|
||||||
pub diag: u8 [try_get BfdDiagnostic] @ 24..=28,
|
pub diag: u8 [try_get BfdDiagnostic] @ 24..=28,
|
||||||
@@ -197,67 +220,211 @@ bitfield! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct BfdAuthSimplePassword(Vec<u8>);
|
pub struct BfdAuthSimplePassword {
|
||||||
|
pub key_id: u8,
|
||||||
|
pub password: Vec<u8>,
|
||||||
|
}
|
||||||
impl<'a> Parse<&'a [u8]> for BfdAuthSimplePassword {
|
impl<'a> Parse<&'a [u8]> for BfdAuthSimplePassword {
|
||||||
fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self, nom::error::Error<&'a [u8]>> {
|
fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self, nom::error::Error<&'a [u8]>> {
|
||||||
let (i, res) = many_m_n(1, 16, be_u8)(i)?;
|
let (i, key_id) = be_u8(i)?;
|
||||||
Ok((i, Self(res)))
|
let (i, password) = many_m_n(1, 16, be_u8)(i)?;
|
||||||
|
Ok((i, Self { key_id, password }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, NomBE)]
|
impl BfdAuthSimplePassword {
|
||||||
|
fn serialize(&self, wtr: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
|
wtr.write_u8(self.key_id)?;
|
||||||
|
wtr.write_all(&self.password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Meticulous and non-meticulous keyed authentication share a wire format and differ only in the
|
||||||
|
/// Auth Type value and sequence number handling.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct BfdAuthKeyedMD5 {
|
pub struct BfdAuthKeyedMD5 {
|
||||||
key_id: u8,
|
pub meticulous: bool,
|
||||||
_reserved: u8,
|
pub key_id: u8,
|
||||||
seq: u32,
|
pub seq: u32,
|
||||||
digest: [u8; 16],
|
pub digest: [u8; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, NomBE)]
|
impl BfdAuthKeyedMD5 {
|
||||||
|
fn parse_with(i: &[u8], meticulous: bool) -> IResult<&[u8], Self> {
|
||||||
|
let (i, key_id) = be_u8(i)?;
|
||||||
|
let (i, _reserved) = be_u8(i)?;
|
||||||
|
let (i, seq) = be_u32(i)?;
|
||||||
|
let (i, digest) = take(16usize)(i)?;
|
||||||
|
let digest = digest.try_into().expect("took 16 bytes");
|
||||||
|
Ok((
|
||||||
|
i,
|
||||||
|
Self {
|
||||||
|
meticulous,
|
||||||
|
key_id,
|
||||||
|
seq,
|
||||||
|
digest,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self, wtr: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
|
wtr.write_u8(self.key_id)?;
|
||||||
|
wtr.write_u8(0)?;
|
||||||
|
wtr.write_u32::<BigEndian>(self.seq)?;
|
||||||
|
wtr.write_all(&self.digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct BfdAuthKeyedSHA1 {
|
pub struct BfdAuthKeyedSHA1 {
|
||||||
key_id: u8,
|
pub meticulous: bool,
|
||||||
_reserved: u8,
|
pub key_id: u8,
|
||||||
seq: u32,
|
pub seq: u32,
|
||||||
hash: [u8; 20],
|
pub digest: [u8; 20],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, NomBE)]
|
impl BfdAuthKeyedSHA1 {
|
||||||
#[nom(Selector = "BfdAuthType", Complete)]
|
fn parse_with(i: &[u8], meticulous: bool) -> IResult<&[u8], Self> {
|
||||||
pub enum BfdAuthData {
|
let (i, key_id) = be_u8(i)?;
|
||||||
#[nom(Selector = "BfdAuthType::SimplePassword")]
|
let (i, _reserved) = be_u8(i)?;
|
||||||
|
let (i, seq) = be_u32(i)?;
|
||||||
|
let (i, digest) = take(20usize)(i)?;
|
||||||
|
let digest = digest.try_into().expect("took 20 bytes");
|
||||||
|
Ok((
|
||||||
|
i,
|
||||||
|
Self {
|
||||||
|
meticulous,
|
||||||
|
key_id,
|
||||||
|
seq,
|
||||||
|
digest,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self, wtr: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
|
wtr.write_u8(self.key_id)?;
|
||||||
|
wtr.write_u8(0)?;
|
||||||
|
wtr.write_u32::<BigEndian>(self.seq)?;
|
||||||
|
wtr.write_all(&self.digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum BfdAuth {
|
||||||
SimplePassword(BfdAuthSimplePassword),
|
SimplePassword(BfdAuthSimplePassword),
|
||||||
#[nom(Selector = "BfdAuthType::KeyedMD5")]
|
|
||||||
KeyedMD5(BfdAuthKeyedMD5),
|
KeyedMD5(BfdAuthKeyedMD5),
|
||||||
#[nom(Selector = "BfdAuthType::MetKeyedMD5")]
|
|
||||||
MetKeyedMD5(BfdAuthKeyedMD5),
|
|
||||||
#[nom(Selector = "BfdAuthType::KeyedSHA1")]
|
|
||||||
KeyedSHA1(BfdAuthKeyedSHA1),
|
KeyedSHA1(BfdAuthKeyedSHA1),
|
||||||
#[nom(Selector = "BfdAuthType::MetKeyedSHA1")]
|
|
||||||
MetKeyedSHA1(BfdAuthKeyedSHA1),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BfdAuthData {
|
impl<'a> Parse<&'a [u8]> for BfdAuth {
|
||||||
fn parse_be_with_length(
|
fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self, nom::error::Error<&'a [u8]>> {
|
||||||
i: &[u8],
|
let (i, auth_type) = BfdAuthType::parse_be(i)?;
|
||||||
auth_type: BfdAuthType,
|
let (i, auth_len) = be_u8(i)?;
|
||||||
auth_len: u8,
|
let (rest, data) = take(auth_len.saturating_sub(2))(i)?;
|
||||||
) -> IResult<&[u8], Self> {
|
let auth = match auth_type {
|
||||||
let (new_i, data) = take(auth_len.saturating_sub(2))(i)?;
|
BfdAuthType::SimplePassword => {
|
||||||
let (_leftovers, retval) = BfdAuthData::parse_be(data, auth_type)?;
|
Self::SimplePassword(BfdAuthSimplePassword::parse(data)?.1)
|
||||||
Ok((new_i, retval))
|
}
|
||||||
|
BfdAuthType::KeyedMD5 => Self::KeyedMD5(BfdAuthKeyedMD5::parse_with(data, false)?.1),
|
||||||
|
BfdAuthType::MetKeyedMD5 => Self::KeyedMD5(BfdAuthKeyedMD5::parse_with(data, true)?.1),
|
||||||
|
BfdAuthType::KeyedSHA1 => Self::KeyedSHA1(BfdAuthKeyedSHA1::parse_with(data, false)?.1),
|
||||||
|
BfdAuthType::MetKeyedSHA1 => {
|
||||||
|
Self::KeyedSHA1(BfdAuthKeyedSHA1::parse_with(data, true)?.1)
|
||||||
|
}
|
||||||
|
BfdAuthType::None | BfdAuthType::Reserved => {
|
||||||
|
return Err(nom::Err::Error(nom::error::Error::new(
|
||||||
|
i,
|
||||||
|
nom::error::ErrorKind::Verify,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((rest, auth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, NomBE)]
|
impl BfdAuth {
|
||||||
pub struct BfdAuth {
|
pub fn auth_type(&self) -> BfdAuthType {
|
||||||
auth_type: BfdAuthType,
|
match self {
|
||||||
auth_len: u8,
|
Self::SimplePassword(_) => BfdAuthType::SimplePassword,
|
||||||
#[nom(Parse = "{ |i| BfdAuthData::parse_be_with_length(i, auth_type, auth_len) }")]
|
Self::KeyedMD5(d) if d.meticulous => BfdAuthType::MetKeyedMD5,
|
||||||
auth_data: BfdAuthData,
|
Self::KeyedMD5(_) => BfdAuthType::KeyedMD5,
|
||||||
|
Self::KeyedSHA1(d) if d.meticulous => BfdAuthType::MetKeyedSHA1,
|
||||||
|
Self::KeyedSHA1(_) => BfdAuthType::KeyedSHA1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, NomBE)]
|
/// Length of the authentication section including the type and length bytes (RFC 5880 4.2-4.4)
|
||||||
|
pub fn auth_len(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(p) => 3 + p.password.len() as u8,
|
||||||
|
Self::KeyedMD5(_) => 24,
|
||||||
|
Self::KeyedSHA1(_) => 28,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sequence number of the keyed variants; simple password has none
|
||||||
|
pub fn seq(&self) -> Option<u32> {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(_) => None,
|
||||||
|
Self::KeyedMD5(d) => Some(d.seq),
|
||||||
|
Self::KeyedSHA1(d) => Some(d.seq),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_seq(&mut self, seq: u32) {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(_) => {}
|
||||||
|
Self::KeyedMD5(d) => d.seq = seq,
|
||||||
|
Self::KeyedSHA1(d) => d.seq = seq,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meticulous(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(_) => false,
|
||||||
|
Self::KeyedMD5(d) => d.meticulous,
|
||||||
|
Self::KeyedSHA1(d) => d.meticulous,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_id(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(d) => d.key_id,
|
||||||
|
Self::KeyedMD5(d) => d.key_id,
|
||||||
|
Self::KeyedSHA1(d) => d.key_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Auth Key/Digest field; holds the key before signing and the digest on the wire
|
||||||
|
pub fn digest_field(&self) -> Option<&[u8]> {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(_) => None,
|
||||||
|
Self::KeyedMD5(d) => Some(&d.digest),
|
||||||
|
Self::KeyedSHA1(d) => Some(&d.digest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn digest_field_mut(&mut self) -> Option<&mut [u8]> {
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(_) => None,
|
||||||
|
Self::KeyedMD5(d) => Some(&mut d.digest),
|
||||||
|
Self::KeyedSHA1(d) => Some(&mut d.digest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self, wtr: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
|
wtr.write_u8(self.auth_type().into())?;
|
||||||
|
wtr.write_u8(self.auth_len())?;
|
||||||
|
match self {
|
||||||
|
Self::SimplePassword(ad) => ad.serialize(wtr),
|
||||||
|
Self::KeyedMD5(ad) => ad.serialize(wtr),
|
||||||
|
Self::KeyedSHA1(ad) => ad.serialize(wtr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, NomBE)]
|
||||||
pub struct BfdPacket {
|
pub struct BfdPacket {
|
||||||
pub flags: BfdFlags,
|
pub flags: BfdFlags,
|
||||||
pub my_disc: BfdDiscriminator,
|
pub my_disc: BfdDiscriminator,
|
||||||
@@ -270,9 +437,10 @@ pub struct BfdPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BfdPacket {
|
impl BfdPacket {
|
||||||
pub fn serialize(&self) -> Result<Box<[u8]>, std::io::Error> {
|
pub fn serialize(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
// TODO: serialize auth
|
let auth_len = self.auth.as_ref().map_or(0, BfdAuth::auth_len);
|
||||||
let buf = [0u8; 24];
|
let buf_len = 24 + auth_len as usize;
|
||||||
|
let buf = Vec::with_capacity(buf_len);
|
||||||
let mut wtr = Cursor::new(buf);
|
let mut wtr = Cursor::new(buf);
|
||||||
wtr.write_u32::<BigEndian>(self.flags.0)?;
|
wtr.write_u32::<BigEndian>(self.flags.0)?;
|
||||||
wtr.write_u32::<BigEndian>(self.my_disc.0)?;
|
wtr.write_u32::<BigEndian>(self.my_disc.0)?;
|
||||||
@@ -281,7 +449,109 @@ impl BfdPacket {
|
|||||||
wtr.write_u32::<BigEndian>(self.required_min_rx.0)?;
|
wtr.write_u32::<BigEndian>(self.required_min_rx.0)?;
|
||||||
wtr.write_u32::<BigEndian>(self.required_min_echo_rx.0)?;
|
wtr.write_u32::<BigEndian>(self.required_min_echo_rx.0)?;
|
||||||
|
|
||||||
Ok(Box::new(wtr.into_inner()))
|
if let Some(ad) = &self.auth {
|
||||||
|
ad.serialize(&mut wtr)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf = wtr.into_inner();
|
||||||
|
assert_eq!(buf.len(), buf_len);
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn digest<D: Digest>(&self) -> std::io::Result<digest::Output<D>> {
|
||||||
|
Ok(D::digest(self.serialize()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Digest of the serialised packet as it currently stands, i.e. with whatever is in the digest field
|
||||||
|
fn compute_digest(&self) -> std::io::Result<Option<Vec<u8>>> {
|
||||||
|
Ok(match &self.auth {
|
||||||
|
Some(BfdAuth::KeyedMD5(_)) => Some(self.digest::<md5::Md5>()?.as_slice().to_vec()),
|
||||||
|
Some(BfdAuth::KeyedSHA1(_)) => Some(self.digest::<sha1::Sha1>()?.as_slice().to_vec()),
|
||||||
|
Some(BfdAuth::SimplePassword(_)) | None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces the key in the digest field with the digest of the packet (RFC 5880 6.7.3)
|
||||||
|
pub fn sign(&mut self) -> std::io::Result<()> {
|
||||||
|
if let Some(digest) = self.compute_digest()? {
|
||||||
|
if let Some(slot) = self.auth.as_mut().and_then(BfdAuth::digest_field_mut) {
|
||||||
|
slot.copy_from_slice(&digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self, expected: &BfdAuth) -> std::io::Result<bool> {
|
||||||
|
if !self.flags.auth_present() {
|
||||||
|
warn!("Auth present flag not set; expected authentication. Failed.");
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
if let Some(ad) = &self.auth {
|
||||||
|
// If the received BFD Control packet does not contain an
|
||||||
|
// Authentication Section, or the Auth Type is not correct,
|
||||||
|
// then the received packet MUST be discarded.
|
||||||
|
match (ad, expected) {
|
||||||
|
(BfdAuth::SimplePassword(sp), BfdAuth::SimplePassword(expected)) => {
|
||||||
|
// If the Auth Key ID field does not match the ID of a configured
|
||||||
|
// password, the received packet MUST be discarded.
|
||||||
|
if sp.key_id != expected.key_id {
|
||||||
|
warn!(
|
||||||
|
"Received key id `{}` doesn't match expected key id `{}`",
|
||||||
|
sp.key_id, expected.key_id
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
// If the Auth Len field is not equal to the length of the password
|
||||||
|
// selected by the key ID, plus three, the packet MUST be discarded.
|
||||||
|
// NB: implied by vec comparison
|
||||||
|
//
|
||||||
|
// If the Password field does not match the password selected by the
|
||||||
|
// key ID, the packet MUST be discarded.
|
||||||
|
Ok(sp.password.ct_eq(&expected.password).into())
|
||||||
|
}
|
||||||
|
(BfdAuth::KeyedMD5(_), BfdAuth::KeyedMD5(_))
|
||||||
|
| (BfdAuth::KeyedSHA1(_), BfdAuth::KeyedSHA1(_)) => {
|
||||||
|
if ad.auth_type() != expected.auth_type() {
|
||||||
|
warn!(
|
||||||
|
"Received auth type {:?} doesn't match expected {:?}",
|
||||||
|
ad.auth_type(),
|
||||||
|
expected.auth_type()
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
// If the Auth Key ID field does not match the ID of a configured
|
||||||
|
// authentication key, the received packet MUST be discarded.
|
||||||
|
if ad.key_id() != expected.key_id() {
|
||||||
|
warn!(
|
||||||
|
"Received key id `{}` doesn't match expected key id `{}`",
|
||||||
|
ad.key_id(),
|
||||||
|
expected.key_id()
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
// Replace the contents of the Auth Key/Digest field with the
|
||||||
|
// authentication key selected by the received Auth Key ID field.
|
||||||
|
let mut keyed = self.clone();
|
||||||
|
match (
|
||||||
|
keyed.auth.as_mut().and_then(BfdAuth::digest_field_mut),
|
||||||
|
expected.digest_field(),
|
||||||
|
) {
|
||||||
|
(Some(slot), Some(key)) => slot.copy_from_slice(key),
|
||||||
|
_ => return Ok(false),
|
||||||
|
}
|
||||||
|
let computed = keyed.compute_digest()?.unwrap_or_default();
|
||||||
|
Ok(computed.ct_eq(ad.digest_field().unwrap_or_default()).into())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Received auth type doesn't match expected, authentication failed");
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Missing auth data, authentication failed");
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,4 +828,323 @@ mod tests {
|
|||||||
assert_eq!(i.checked_mul(4), Some(BfdInterval::from_secs(4000)));
|
assert_eq!(i.checked_mul(4), Some(BfdInterval::from_secs(4000)));
|
||||||
assert_eq!(i.checked_mul(5), None);
|
assert_eq!(i.checked_mul(5), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MD5_KEY: [u8; 16] = *b"0123456789abcdef";
|
||||||
|
const SHA1_KEY: [u8; 20] = *b"0123456789abcdefghij";
|
||||||
|
// Independently computed (hashlib) over the full packet with the key in the digest field
|
||||||
|
const MD5_MET_DIGEST: &str = "d739c4fa15bc2468315d20302f2081d5";
|
||||||
|
const MD5_NONMET_DIGEST: &str = "d0a336d884839041be9e4d99aa713044";
|
||||||
|
const SHA1_MET_DIGEST: &str = "b47f39e2b986e3c5ba7525244885cf6628d59974";
|
||||||
|
|
||||||
|
fn hex(bytes: &[u8]) -> String {
|
||||||
|
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn md5_auth(meticulous: bool, key_id: u8, seq: u32, key: [u8; 16]) -> BfdAuth {
|
||||||
|
BfdAuth::KeyedMD5(BfdAuthKeyedMD5 {
|
||||||
|
meticulous,
|
||||||
|
key_id,
|
||||||
|
seq,
|
||||||
|
digest: key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sha1_auth(meticulous: bool, key_id: u8, seq: u32, key: [u8; 20]) -> BfdAuth {
|
||||||
|
BfdAuth::KeyedSHA1(BfdAuthKeyedSHA1 {
|
||||||
|
meticulous,
|
||||||
|
key_id,
|
||||||
|
seq,
|
||||||
|
digest: key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn password_auth(key_id: u8, password: &[u8]) -> BfdAuth {
|
||||||
|
BfdAuth::SimplePassword(BfdAuthSimplePassword {
|
||||||
|
key_id,
|
||||||
|
password: password.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn auth_packet(auth: Option<BfdAuth>) -> BfdPacket {
|
||||||
|
let len = 24 + auth.as_ref().map_or(0, BfdAuth::auth_len);
|
||||||
|
BfdPacket {
|
||||||
|
flags: BfdFlags(0)
|
||||||
|
.with_vers(1)
|
||||||
|
.with_state(BfdState::Up as u8)
|
||||||
|
.with_detect_mult(3)
|
||||||
|
.with_length(len)
|
||||||
|
.with_auth_present(auth.is_some()),
|
||||||
|
my_disc: BfdDiscriminator(1),
|
||||||
|
your_disc: BfdDiscriminator(2),
|
||||||
|
desired_min_tx: BfdInterval::from_millis(300),
|
||||||
|
required_min_rx: BfdInterval::from_millis(300),
|
||||||
|
required_min_echo_rx: BfdInterval::from_micros(0),
|
||||||
|
auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign, serialise and re-parse, as a packet crosses the wire
|
||||||
|
fn over_the_wire(auth: BfdAuth) -> BfdPacket {
|
||||||
|
let mut p = auth_packet(Some(auth));
|
||||||
|
p.sign().unwrap();
|
||||||
|
parse(&p.serialize().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_accessors() {
|
||||||
|
let md5 = md5_auth(true, 7, 9, MD5_KEY);
|
||||||
|
assert_eq!(md5.auth_type(), BfdAuthType::MetKeyedMD5);
|
||||||
|
assert_eq!(md5.auth_len(), 24);
|
||||||
|
assert_eq!(
|
||||||
|
(md5.seq(), md5.key_id(), md5.meticulous()),
|
||||||
|
(Some(9), 7, true)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
md5_auth(false, 0, 0, MD5_KEY).auth_type(),
|
||||||
|
BfdAuthType::KeyedMD5
|
||||||
|
);
|
||||||
|
|
||||||
|
let sha1 = sha1_auth(true, 1, 2, SHA1_KEY);
|
||||||
|
assert_eq!(sha1.auth_type(), BfdAuthType::MetKeyedSHA1);
|
||||||
|
assert_eq!(sha1.auth_len(), 28);
|
||||||
|
assert_eq!(
|
||||||
|
sha1_auth(false, 0, 0, SHA1_KEY).auth_type(),
|
||||||
|
BfdAuthType::KeyedSHA1
|
||||||
|
);
|
||||||
|
|
||||||
|
let pw = password_auth(3, b"secret");
|
||||||
|
assert_eq!(pw.auth_type(), BfdAuthType::SimplePassword);
|
||||||
|
assert_eq!(pw.auth_len(), 9);
|
||||||
|
assert_eq!((pw.seq(), pw.key_id(), pw.meticulous()), (None, 3, false));
|
||||||
|
assert!(pw.digest_field().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_seq_only_affects_keyed() {
|
||||||
|
let mut md5 = md5_auth(true, 1, 0, MD5_KEY);
|
||||||
|
md5.set_seq(42);
|
||||||
|
assert_eq!(md5.seq(), Some(42));
|
||||||
|
let mut sha1 = sha1_auth(true, 1, 0, SHA1_KEY);
|
||||||
|
sha1.set_seq(43);
|
||||||
|
assert_eq!(sha1.seq(), Some(43));
|
||||||
|
let mut pw = password_auth(1, b"pw");
|
||||||
|
pw.set_seq(44);
|
||||||
|
assert_eq!(pw.seq(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyed_auth_wire_roundtrip() {
|
||||||
|
for auth in [
|
||||||
|
md5_auth(false, 7, 0xdead_beef, MD5_KEY),
|
||||||
|
md5_auth(true, 7, 1, MD5_KEY),
|
||||||
|
sha1_auth(false, 255, u32::MAX, SHA1_KEY),
|
||||||
|
sha1_auth(true, 0, 0, SHA1_KEY),
|
||||||
|
] {
|
||||||
|
let p = auth_packet(Some(auth.clone()));
|
||||||
|
let bytes = p.serialize().unwrap();
|
||||||
|
assert_eq!(bytes.len(), 24 + auth.auth_len() as usize);
|
||||||
|
assert_eq!(bytes[3] as usize, bytes.len());
|
||||||
|
assert_eq!(bytes[24], u8::from(auth.auth_type()));
|
||||||
|
assert_eq!(bytes[25], auth.auth_len());
|
||||||
|
assert_eq!(parse(&bytes).auth, Some(auth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn password_auth_wire_roundtrip() {
|
||||||
|
for pw in [&b"a"[..], b"secret", b"0123456789abcdef"] {
|
||||||
|
let auth = password_auth(5, pw);
|
||||||
|
let p = auth_packet(Some(auth.clone()));
|
||||||
|
let bytes = p.serialize().unwrap();
|
||||||
|
assert_eq!(bytes.len(), 24 + 3 + pw.len());
|
||||||
|
assert_eq!(bytes[24..27], [1, 3 + pw.len() as u8, 5]);
|
||||||
|
assert_eq!(&bytes[27..], pw);
|
||||||
|
assert_eq!(parse(&bytes).auth, Some(auth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rejects_bad_auth_sections() {
|
||||||
|
let mut base = UP_PACKET.to_vec();
|
||||||
|
base[1] |= 0x04;
|
||||||
|
// Auth Type 0 (None) and an unassigned type are not valid with the A bit set
|
||||||
|
for auth_type in [0u8, 6] {
|
||||||
|
let mut bytes = base.clone();
|
||||||
|
bytes.extend_from_slice(&[auth_type, 24, 1, 0, 0, 0, 0, 1]);
|
||||||
|
bytes.extend_from_slice(&[0; 16]);
|
||||||
|
assert!(BfdPacket::parse(&bytes).is_err(), "type {auth_type}");
|
||||||
|
}
|
||||||
|
// Truncated digest
|
||||||
|
let mut bytes = base.clone();
|
||||||
|
bytes.extend_from_slice(&[2, 24, 1, 0, 0, 0, 0, 1]);
|
||||||
|
bytes.extend_from_slice(&[0; 8]);
|
||||||
|
assert!(BfdPacket::parse(&bytes).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_matches_known_md5_digest() {
|
||||||
|
let mut p = auth_packet(Some(md5_auth(true, 7, 9, MD5_KEY)));
|
||||||
|
assert_eq!(p.serialize().unwrap()[..4], [0x20, 0xc4, 0x03, 0x30]);
|
||||||
|
p.sign().unwrap();
|
||||||
|
let digest = p.auth.as_ref().unwrap().digest_field().unwrap();
|
||||||
|
assert_eq!(hex(digest), MD5_MET_DIGEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_matches_known_sha1_digest() {
|
||||||
|
let mut p = auth_packet(Some(sha1_auth(true, 7, 9, SHA1_KEY)));
|
||||||
|
p.sign().unwrap();
|
||||||
|
let digest = p.auth.as_ref().unwrap().digest_field().unwrap();
|
||||||
|
assert_eq!(hex(digest), SHA1_MET_DIGEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn meticulous_flag_changes_digest() {
|
||||||
|
let mut p = auth_packet(Some(md5_auth(false, 7, 9, MD5_KEY)));
|
||||||
|
p.sign().unwrap();
|
||||||
|
let digest = p.auth.as_ref().unwrap().digest_field().unwrap();
|
||||||
|
assert_eq!(hex(digest), MD5_NONMET_DIGEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_does_not_leak_key() {
|
||||||
|
for (auth, key) in [
|
||||||
|
(md5_auth(true, 7, 9, MD5_KEY), &MD5_KEY[..]),
|
||||||
|
(sha1_auth(true, 7, 9, SHA1_KEY), &SHA1_KEY[..]),
|
||||||
|
] {
|
||||||
|
let bytes = over_the_wire(auth).serialize().unwrap();
|
||||||
|
assert!(!bytes.windows(key.len()).any(|w| w == key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_is_a_noop_without_keyed_auth() {
|
||||||
|
let mut none = auth_packet(None);
|
||||||
|
none.sign().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
none.serialize().unwrap(),
|
||||||
|
parse(&UP_PACKET).serialize().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pw = auth_packet(Some(password_auth(1, b"secret")));
|
||||||
|
let before = pw.serialize().unwrap();
|
||||||
|
pw.sign().unwrap();
|
||||||
|
assert_eq!(pw.serialize().unwrap(), before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_accepts_valid_keyed_packets() {
|
||||||
|
for (sent, expected) in [
|
||||||
|
(md5_auth(true, 7, 9, MD5_KEY), md5_auth(true, 7, 0, MD5_KEY)),
|
||||||
|
(
|
||||||
|
md5_auth(false, 7, 9, MD5_KEY),
|
||||||
|
md5_auth(false, 7, 0, MD5_KEY),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sha1_auth(true, 7, 9, SHA1_KEY),
|
||||||
|
sha1_auth(true, 7, 0, SHA1_KEY),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sha1_auth(false, 7, 9, SHA1_KEY),
|
||||||
|
sha1_auth(false, 7, 0, SHA1_KEY),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
assert!(over_the_wire(sent).verify(&expected).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_does_not_depend_on_expected_seq() {
|
||||||
|
let p = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
assert!(p.verify(&md5_auth(true, 7, 12345, MD5_KEY)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_wrong_key() {
|
||||||
|
let mut other = MD5_KEY;
|
||||||
|
other[15] ^= 1;
|
||||||
|
let p = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
assert!(!p.verify(&md5_auth(true, 7, 0, other)).unwrap());
|
||||||
|
|
||||||
|
let mut other = SHA1_KEY;
|
||||||
|
other[0] ^= 1;
|
||||||
|
let p = over_the_wire(sha1_auth(true, 7, 9, SHA1_KEY));
|
||||||
|
assert!(!p.verify(&sha1_auth(true, 7, 0, other)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_wrong_key_id() {
|
||||||
|
let p = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
assert!(!p.verify(&md5_auth(true, 8, 0, MD5_KEY)).unwrap());
|
||||||
|
let p = over_the_wire(sha1_auth(true, 7, 9, SHA1_KEY));
|
||||||
|
assert!(!p.verify(&sha1_auth(true, 8, 0, SHA1_KEY)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_meticulous_mismatch() {
|
||||||
|
let p = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
assert!(!p.verify(&md5_auth(false, 7, 0, MD5_KEY)).unwrap());
|
||||||
|
let p = over_the_wire(sha1_auth(false, 7, 9, SHA1_KEY));
|
||||||
|
assert!(!p.verify(&sha1_auth(true, 7, 0, SHA1_KEY)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_auth_type_mismatch() {
|
||||||
|
let md5 = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
assert!(!md5.verify(&sha1_auth(true, 7, 0, SHA1_KEY)).unwrap());
|
||||||
|
assert!(!md5.verify(&password_auth(7, b"secret")).unwrap());
|
||||||
|
let sha1 = over_the_wire(sha1_auth(true, 7, 9, SHA1_KEY));
|
||||||
|
assert!(!sha1.verify(&md5_auth(true, 7, 0, MD5_KEY)).unwrap());
|
||||||
|
let pw = over_the_wire(password_auth(7, b"secret"));
|
||||||
|
assert!(!pw.verify(&md5_auth(true, 7, 0, MD5_KEY)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_tampered_packet() {
|
||||||
|
let sent = over_the_wire(md5_auth(true, 7, 9, MD5_KEY));
|
||||||
|
let expected = md5_auth(true, 7, 0, MD5_KEY);
|
||||||
|
assert!(sent.verify(&expected).unwrap());
|
||||||
|
|
||||||
|
let mut p = sent.clone();
|
||||||
|
p.my_disc = BfdDiscriminator(99);
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
|
||||||
|
let mut p = sent.clone();
|
||||||
|
p.flags = p.flags.with_state(BfdState::Down as u8);
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
|
||||||
|
let mut p = sent.clone();
|
||||||
|
p.desired_min_tx = BfdInterval::from_micros(1);
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
|
||||||
|
let mut p = sent.clone();
|
||||||
|
p.auth.as_mut().unwrap().set_seq(10);
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
|
||||||
|
let mut p = sent;
|
||||||
|
if let Some(BfdAuth::KeyedMD5(d)) = p.auth.as_mut() {
|
||||||
|
d.digest[0] ^= 1;
|
||||||
|
}
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_rejects_missing_auth() {
|
||||||
|
let expected = md5_auth(true, 7, 0, MD5_KEY);
|
||||||
|
assert!(!auth_packet(None).verify(&expected).unwrap());
|
||||||
|
// A bit set but no section
|
||||||
|
let mut p = auth_packet(None);
|
||||||
|
p.flags = p.flags.with_auth_present(true);
|
||||||
|
assert!(!p.verify(&expected).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_simple_password() {
|
||||||
|
let p = over_the_wire(password_auth(5, b"secret"));
|
||||||
|
assert!(p.verify(&password_auth(5, b"secret")).unwrap());
|
||||||
|
assert!(!p.verify(&password_auth(5, b"secreT")).unwrap());
|
||||||
|
assert!(!p.verify(&password_auth(5, b"secrets")).unwrap());
|
||||||
|
assert!(!p.verify(&password_auth(5, b"secre")).unwrap());
|
||||||
|
assert!(!p.verify(&password_auth(6, b"secret")).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+174
-21
@@ -3,7 +3,9 @@ use futures::future;
|
|||||||
use rust_bfd::config::{Config, ConfigError, ResolvedConfig, ResolvedPeer};
|
use rust_bfd::config::{Config, ConfigError, ResolvedConfig, ResolvedPeer};
|
||||||
use rust_bfd::events::{EventMessageSink, ScriptHookSink, StateChangeEvent};
|
use rust_bfd::events::{EventMessageSink, ScriptHookSink, StateChangeEvent};
|
||||||
use rust_bfd::{bfd::*, events::EventMessage};
|
use rust_bfd::{bfd::*, events::EventMessage};
|
||||||
use rust_bfd::{set_ttl_or_hop_limit, AtomicInstant, AtomicIntervalStats, BfdSessionStats};
|
use rust_bfd::{
|
||||||
|
set_ttl_or_hop_limit, AtomicInstant, AtomicIntervalStats, AuthStats, BfdSessionStats,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
error::Error,
|
error::Error,
|
||||||
@@ -98,6 +100,14 @@ impl Display for BfdError {
|
|||||||
}
|
}
|
||||||
impl Error for BfdError {}
|
impl Error for BfdError {}
|
||||||
|
|
||||||
|
/// RFC 5880 6.7.3: a received sequence number must lie in [last, last + 3 * detect_mult] (or [last + 1, ..] when
|
||||||
|
/// meticulous), treated as an unsigned 32-bit circular number space.
|
||||||
|
fn seq_in_window(last: u32, seq: u32, detect_mult: u8, meticulous: bool) -> bool {
|
||||||
|
let lo = last.wrapping_add(meticulous as u32);
|
||||||
|
let hi = last.wrapping_add(3 * detect_mult as u32);
|
||||||
|
seq.wrapping_sub(lo) <= hi.wrapping_sub(lo)
|
||||||
|
}
|
||||||
|
|
||||||
/// Data structure to store the state of the Bfd machine. The impl does *not*
|
/// Data structure to store the state of the Bfd machine. The impl does *not*
|
||||||
/// implement any Bfd logic, it is merely a thread-safe data structure
|
/// implement any Bfd logic, it is merely a thread-safe data structure
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -116,10 +126,10 @@ struct BfdSessionState {
|
|||||||
demand_mode: Atomic<bool>, // mutable by rx thread
|
demand_mode: Atomic<bool>, // mutable by rx thread
|
||||||
remote_demand_mode: Atomic<bool>, // mutable by rx thread
|
remote_demand_mode: Atomic<bool>, // mutable by rx thread
|
||||||
detect_mult: Atomic<u8>, // mutable by control thread
|
detect_mult: Atomic<u8>, // mutable by control thread
|
||||||
auth_type: Atomic<BfdAuthType>, // mutable by control thread
|
rcv_auth_seq: Mutex<u32>, // must guard validation against parallel reception
|
||||||
rcv_auth_seq: Atomic<u32>, // mutable by rx thread
|
xmit_auth_seq: Mutex<u32>, // must guard against OOO parallel transmission e.g. in rx task
|
||||||
xmit_auth_seq: Atomic<u32>, // mutable by tx thread?
|
|
||||||
auth_seq_known: Atomic<bool>, // mutable by rx thread?
|
auth_seq_known: Atomic<bool>, // mutable by rx thread?
|
||||||
|
auth_type: Atomic<BfdAuthType>, // immutable after construction
|
||||||
|
|
||||||
detection_time: Atomic<BfdInterval>, // mutable by rx thread
|
detection_time: Atomic<BfdInterval>, // mutable by rx thread
|
||||||
poll_mode: Atomic<bool>, // mutable by rx thread
|
poll_mode: Atomic<bool>, // mutable by rx thread
|
||||||
@@ -193,6 +203,12 @@ impl BfdSessionState {
|
|||||||
fn auth_type(&self) -> BfdAuthType {
|
fn auth_type(&self) -> BfdAuthType {
|
||||||
self.auth_type.load(ORDERING)
|
self.auth_type.load(ORDERING)
|
||||||
}
|
}
|
||||||
|
fn auth_seq_known(&self) -> bool {
|
||||||
|
self.auth_seq_known.load(ORDERING)
|
||||||
|
}
|
||||||
|
fn set_auth_seq_known(&self, value: bool) {
|
||||||
|
self.auth_seq_known.store(value, ORDERING)
|
||||||
|
}
|
||||||
fn detect_mult(&self) -> u8 {
|
fn detect_mult(&self) -> u8 {
|
||||||
self.detect_mult.load(ORDERING)
|
self.detect_mult.load(ORDERING)
|
||||||
}
|
}
|
||||||
@@ -265,6 +281,7 @@ struct BfdSession {
|
|||||||
// DesiredMinTxInterval actually used for the transmit rate; lags the advertised value during a Poll Sequence
|
// DesiredMinTxInterval actually used for the transmit rate; lags the advertised value during a Poll Sequence
|
||||||
effective_desired_min_tx: BfdInterval,
|
effective_desired_min_tx: BfdInterval,
|
||||||
event_dispatcher: EventDispatcherHandle,
|
event_dispatcher: EventDispatcherHandle,
|
||||||
|
auth: Option<BfdAuth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&BfdSessionState> for BfdSessionStats {
|
impl From<&BfdSessionState> for BfdSessionStats {
|
||||||
@@ -283,6 +300,7 @@ impl From<&BfdSessionState> for BfdSessionStats {
|
|||||||
last_change: state.last_state_change(),
|
last_change: state.last_state_change(),
|
||||||
detect_time: state.detection_time(),
|
detect_time: state.detection_time(),
|
||||||
base_interval: BfdInterval::from_secs(0), // TODO: This is a hack and will be patched up in the session
|
base_interval: BfdInterval::from_secs(0), // TODO: This is a hack and will be patched up in the session
|
||||||
|
auth: None, // filled in by the session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -463,6 +481,9 @@ impl BfdSession {
|
|||||||
// .connect(SocketAddr::new(remote_addr, CONTROL_PORT))
|
// .connect(SocketAddr::new(remote_addr, CONTROL_PORT))
|
||||||
// .await?;
|
// .await?;
|
||||||
|
|
||||||
|
let auth: Option<BfdAuth> = peer.auth.map(Into::into);
|
||||||
|
let auth_type = auth.as_ref().map_or(BfdAuthType::None, BfdAuth::auth_type);
|
||||||
|
|
||||||
// Incoming packets will come over the channel from the mux, since they don't send to the reciprocal port
|
// Incoming packets will come over the channel from the mux, since they don't send to the reciprocal port
|
||||||
Self {
|
Self {
|
||||||
rx,
|
rx,
|
||||||
@@ -473,6 +494,7 @@ impl BfdSession {
|
|||||||
configured_desired_min_tx,
|
configured_desired_min_tx,
|
||||||
effective_desired_min_tx: slow_desired_min_tx,
|
effective_desired_min_tx: slow_desired_min_tx,
|
||||||
event_dispatcher,
|
event_dispatcher,
|
||||||
|
auth,
|
||||||
state: BfdSessionState {
|
state: BfdSessionState {
|
||||||
control_sock: Arc::new(control_sock),
|
control_sock: Arc::new(control_sock),
|
||||||
local_ip: local_addr,
|
local_ip: local_addr,
|
||||||
@@ -488,10 +510,10 @@ impl BfdSession {
|
|||||||
demand_mode: Atomic::new(false),
|
demand_mode: Atomic::new(false),
|
||||||
remote_demand_mode: Atomic::new(false),
|
remote_demand_mode: Atomic::new(false),
|
||||||
detect_mult: Atomic::new(peer.detect_mult),
|
detect_mult: Atomic::new(peer.detect_mult),
|
||||||
auth_type: Atomic::new(BfdAuthType::None),
|
rcv_auth_seq: Mutex::new(0),
|
||||||
rcv_auth_seq: Atomic::new(0),
|
xmit_auth_seq: Mutex::new(rng.gen()),
|
||||||
xmit_auth_seq: Atomic::new(rng.gen()),
|
|
||||||
auth_seq_known: Atomic::new(false),
|
auth_seq_known: Atomic::new(false),
|
||||||
|
auth_type: Atomic::new(auth_type),
|
||||||
detection_time: Atomic::new(BfdInterval::from_secs(0)),
|
detection_time: Atomic::new(BfdInterval::from_secs(0)),
|
||||||
poll_mode: Atomic::new(false),
|
poll_mode: Atomic::new(false),
|
||||||
|
|
||||||
@@ -568,16 +590,41 @@ impl BfdSession {
|
|||||||
self.transmit_control_packet(false).await;
|
self.transmit_control_packet(false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn auth_stats(&self) -> Option<AuthStats> {
|
||||||
|
let auth = self.auth.as_ref()?;
|
||||||
|
let uses_seq = !matches!(auth, BfdAuth::SimplePassword(_));
|
||||||
|
let rcv_seq = if uses_seq && self.state.auth_seq_known() {
|
||||||
|
Some(*self.state.rcv_auth_seq.lock().await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let xmit_seq = if uses_seq {
|
||||||
|
Some(*self.state.xmit_auth_seq.lock().await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Some(AuthStats {
|
||||||
|
auth_type: self.state.auth_type(),
|
||||||
|
key_id: auth.key_id(),
|
||||||
|
rcv_seq,
|
||||||
|
xmit_seq,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn run(&mut self) {
|
async fn run(&mut self) {
|
||||||
while let Some(cmd) = self.rx.recv().await {
|
while let Some(cmd) = self.rx.recv().await {
|
||||||
|
let mut missed_detect_times = 0;
|
||||||
match cmd {
|
match cmd {
|
||||||
SessionControlCommand::Quit => return,
|
SessionControlCommand::Quit => return,
|
||||||
SessionControlCommand::RxPacket(packet) => {
|
SessionControlCommand::RxPacket(packet) => {
|
||||||
|
missed_detect_times = 0;
|
||||||
self.receive_control_packet(&packet).await;
|
self.receive_control_packet(&packet).await;
|
||||||
}
|
}
|
||||||
SessionControlCommand::GetSessionStats { respond_to } => {
|
SessionControlCommand::GetSessionStats { respond_to } => {
|
||||||
let stats =
|
let auth = self.auth_stats().await;
|
||||||
BfdSessionStats::from(&self.state).with_base_interval(self.cur_interval);
|
let stats = BfdSessionStats::from(&self.state)
|
||||||
|
.with_base_interval(self.cur_interval)
|
||||||
|
.with_auth(auth);
|
||||||
respond_to.send(stats).unwrap()
|
respond_to.send(stats).unwrap()
|
||||||
}
|
}
|
||||||
SessionControlCommand::TxControlPacket { poll_response } => {
|
SessionControlCommand::TxControlPacket { poll_response } => {
|
||||||
@@ -585,15 +632,19 @@ impl BfdSession {
|
|||||||
}
|
}
|
||||||
SessionControlCommand::ReceiveTimeout => {
|
SessionControlCommand::ReceiveTimeout => {
|
||||||
self.state.set_local_diag(BfdDiagnostic::TimeExpired);
|
self.state.set_local_diag(BfdDiagnostic::TimeExpired);
|
||||||
|
missed_detect_times += 1;
|
||||||
self.transition(BfdState::Down).await;
|
self.transition(BfdState::Down).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if missed_detect_times == 2 {
|
||||||
|
self.state.set_auth_seq_known(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
println!("Session command channel closed");
|
println!("Session command channel closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn transmit_control_packet(&self, poll_response: bool) {
|
async fn transmit_control_packet(&mut self, poll_response: bool) {
|
||||||
let packet = BfdPacket {
|
let mut packet = BfdPacket {
|
||||||
flags: BfdFlags(0)
|
flags: BfdFlags(0)
|
||||||
.with_vers(1)
|
.with_vers(1)
|
||||||
.with_diag(self.state.local_diag().into())
|
.with_diag(self.state.local_diag().into())
|
||||||
@@ -607,18 +658,32 @@ impl BfdSession {
|
|||||||
.with_cpi(false)
|
.with_cpi(false)
|
||||||
.with_demand(self.state.demand_mode())
|
.with_demand(self.state.demand_mode())
|
||||||
.with_detect_mult(self.state.detect_mult())
|
.with_detect_mult(self.state.detect_mult())
|
||||||
.with_length(24),
|
.with_length(24 + self.auth.as_ref().map_or(0, BfdAuth::auth_len))
|
||||||
|
.with_auth_present(self.auth.is_some()),
|
||||||
my_disc: self.state.local_discr(),
|
my_disc: self.state.local_discr(),
|
||||||
your_disc: self.state.remote_discr(),
|
your_disc: self.state.remote_discr(),
|
||||||
desired_min_tx: self.state.desired_min_tx_interval(),
|
desired_min_tx: self.state.desired_min_tx_interval(),
|
||||||
required_min_rx: self.state.required_min_rx_interval(),
|
required_min_rx: self.state.required_min_rx_interval(),
|
||||||
required_min_echo_rx: BfdInterval::from_secs(0),
|
required_min_echo_rx: BfdInterval::from_secs(0),
|
||||||
auth: None,
|
auth: self.auth.clone(), // put configured method and key into packet
|
||||||
};
|
};
|
||||||
let socket = self.state.control_sock.clone();
|
let socket = self.state.control_sock.clone();
|
||||||
let dest = self.state.peer_addr;
|
let dest = self.state.peer_addr;
|
||||||
|
|
||||||
|
// Held until the packet is sent so sequence numbers hit the wire in order
|
||||||
|
let mut xmit_seq = match packet.auth {
|
||||||
|
Some(_) => Some(self.state.xmit_auth_seq.lock().await),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let (Some(auth), Some(seq)) = (&mut packet.auth, xmit_seq.as_mut()) {
|
||||||
|
**seq = seq.wrapping_add(1);
|
||||||
|
auth.set_seq(**seq);
|
||||||
|
}
|
||||||
|
packet.sign().unwrap();
|
||||||
|
|
||||||
self.state.record_tx();
|
self.state.record_tx();
|
||||||
debug!("tx packet: {:?}", packet);
|
debug!("tx packet: {:?}", packet);
|
||||||
|
|
||||||
if let Err(e) = socket
|
if let Err(e) = socket
|
||||||
.send_to(
|
.send_to(
|
||||||
packet.serialize().unwrap().as_ref(),
|
packet.serialize().unwrap().as_ref(),
|
||||||
@@ -755,7 +820,6 @@ impl BfdSession {
|
|||||||
warn!("Got packet with auth enabled when we disagree, discarding");
|
warn!("Got packet with auth enabled when we disagree, discarding");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the A bit is clear and authentication is in use (bfd.AuthType is nonzero), the packet MUST be discarded.
|
// If the A bit is clear and authentication is in use (bfd.AuthType is nonzero), the packet MUST be discarded.
|
||||||
if !p.flags.auth_present() && self.state.auth_type() != BfdAuthType::None {
|
if !p.flags.auth_present() && self.state.auth_type() != BfdAuthType::None {
|
||||||
warn!("Got packet without auth when we expect it, discarding");
|
warn!("Got packet without auth when we expect it, discarding");
|
||||||
@@ -764,8 +828,48 @@ impl BfdSession {
|
|||||||
|
|
||||||
// If the A bit is set, the packet MUST be authenticated under the rules of section 6.7, based on the
|
// If the A bit is set, the packet MUST be authenticated under the rules of section 6.7, based on the
|
||||||
// authentication type in use (bfd.AuthType). This may cause the packet to be discarded.
|
// authentication type in use (bfd.AuthType). This may cause the packet to be discarded.
|
||||||
if p.flags.auth_present() {
|
if let Some(expected) = self.auth.as_ref().filter(|_| p.flags.auth_present()) {
|
||||||
unimplemented!("Authentication is not implemented");
|
let received_seq = p.auth.as_ref().and_then(BfdAuth::seq);
|
||||||
|
let mut rcv_auth_seq = self.state.rcv_auth_seq.lock().await;
|
||||||
|
|
||||||
|
if let Some(seq) = received_seq {
|
||||||
|
// If bfd.AuthSeqKnown is 1, examine the Sequence Number field. [...] if the sequence number lies
|
||||||
|
// outside of the valid range the received packet MUST be discarded.
|
||||||
|
if self.state.auth_seq_known() {
|
||||||
|
if !seq_in_window(
|
||||||
|
*rcv_auth_seq,
|
||||||
|
seq,
|
||||||
|
p.flags.detect_mult(),
|
||||||
|
expected.meticulous(),
|
||||||
|
) {
|
||||||
|
warn!(
|
||||||
|
"Got packet with out of window sequence number {} (last {}), discarding",
|
||||||
|
seq, *rcv_auth_seq
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise (bfd.AuthSeqKnown is 0), set bfd.AuthSeqKnown to 1.
|
||||||
|
self.state.set_auth_seq_known(true);
|
||||||
|
}
|
||||||
|
// bfd.RcvAuthSeq is set to the Sequence Number field.
|
||||||
|
*rcv_auth_seq = seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
match p.verify(expected) {
|
||||||
|
Ok(true) => {}
|
||||||
|
Ok(false) => {
|
||||||
|
warn!("Got packet failing authentication, discarding");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"Error verifying packet authentication ({:?}), discarding",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set bfd.RemoteDiscr to the value of My Discriminator.
|
// Set bfd.RemoteDiscr to the value of My Discriminator.
|
||||||
@@ -1083,7 +1187,7 @@ impl Bfdd {
|
|||||||
}
|
}
|
||||||
let session = BfdSessionHandle::new(
|
let session = BfdSessionHandle::new(
|
||||||
self.local.ip(),
|
self.local.ip(),
|
||||||
*peer,
|
peer.clone(),
|
||||||
local_discr,
|
local_discr,
|
||||||
self.event_dispatcher.clone(),
|
self.event_dispatcher.clone(),
|
||||||
)
|
)
|
||||||
@@ -1164,13 +1268,22 @@ impl Bfdd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn session_report(&self) -> String {
|
async fn session_report(&self) -> String {
|
||||||
future::join_all(
|
let mut stats = future::join_all(
|
||||||
self.sessions_by_discr
|
self.sessions_by_discr
|
||||||
.values()
|
.values()
|
||||||
.map(|session| async { session.get_stats().await.unwrap().to_string() }),
|
.map(|session| async { session.get_stats().await.unwrap() }),
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.join("\n\t")
|
stats.sort_by_key(|s| s.remote_ip);
|
||||||
|
stats
|
||||||
|
.iter()
|
||||||
|
.flat_map(|s| {
|
||||||
|
s.to_string()
|
||||||
|
.lines()
|
||||||
|
.map(|l| format!("\n {l}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
async fn handle_datagram(&self, data: &[u8], addr: SocketAddr) {
|
async fn handle_datagram(&self, data: &[u8], addr: SocketAddr) {
|
||||||
let Ok((_leftover, packet)) = BfdPacket::parse(data) else {
|
let Ok((_leftover, packet)) = BfdPacket::parse(data) else {
|
||||||
@@ -1248,6 +1361,45 @@ async fn main() -> io::Result<()> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seq_window_non_meticulous_bounds() {
|
||||||
|
// RFC 5880 6.7.3: [RcvAuthSeq, RcvAuthSeq + 3*DetectMult]
|
||||||
|
assert!(seq_in_window(100, 100, 3, false));
|
||||||
|
assert!(seq_in_window(100, 101, 3, false));
|
||||||
|
assert!(seq_in_window(100, 109, 3, false));
|
||||||
|
assert!(!seq_in_window(100, 110, 3, false));
|
||||||
|
assert!(!seq_in_window(100, 99, 3, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seq_window_meticulous_bounds() {
|
||||||
|
// RFC 5880 6.7.4: [RcvAuthSeq + 1, RcvAuthSeq + 3*DetectMult]
|
||||||
|
assert!(!seq_in_window(100, 100, 3, true));
|
||||||
|
assert!(seq_in_window(100, 101, 3, true));
|
||||||
|
assert!(seq_in_window(100, 109, 3, true));
|
||||||
|
assert!(!seq_in_window(100, 110, 3, true));
|
||||||
|
assert!(!seq_in_window(100, 99, 3, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seq_window_scales_with_detect_mult() {
|
||||||
|
assert!(seq_in_window(0, 3, 1, false));
|
||||||
|
assert!(!seq_in_window(0, 4, 1, false));
|
||||||
|
assert!(seq_in_window(0, 255 * 3, 255, false));
|
||||||
|
assert!(!seq_in_window(0, 255 * 3 + 1, 255, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seq_window_wraps_at_u32_max() {
|
||||||
|
assert!(seq_in_window(u32::MAX, u32::MAX, 3, false));
|
||||||
|
assert!(!seq_in_window(u32::MAX, u32::MAX, 3, true));
|
||||||
|
assert!(seq_in_window(u32::MAX, 0, 3, true));
|
||||||
|
assert!(seq_in_window(u32::MAX - 2, 6, 3, true));
|
||||||
|
assert!(!seq_in_window(u32::MAX - 2, 7, 3, true));
|
||||||
|
assert!(!seq_in_window(u32::MAX - 2, u32::MAX - 3, 3, true));
|
||||||
|
assert!(!seq_in_window(5, u32::MAX, 3, true));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jitter_is_uniform_over_rfc_range() {
|
fn jitter_is_uniform_over_rfc_range() {
|
||||||
// RFC 5880 6.8.7: each interval is reduced by a random 0-25% (0-10% when DetectMult is 1), so delays are
|
// RFC 5880 6.8.7: each interval is reduced by a random 0-25% (0-10% when DetectMult is 1), so delays are
|
||||||
@@ -1384,6 +1536,7 @@ mod tests {
|
|||||||
desired_min_tx: BfdInterval::from_millis(desired_ms),
|
desired_min_tx: BfdInterval::from_millis(desired_ms),
|
||||||
required_min_rx: BfdInterval::from_millis(required_ms),
|
required_min_rx: BfdInterval::from_millis(required_ms),
|
||||||
detect_mult,
|
detect_mult,
|
||||||
|
auth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+83
-7
@@ -17,8 +17,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_valid::Validate;
|
||||||
|
|
||||||
use crate::bfd::BfdInterval;
|
use crate::bfd::{BfdAuth, BfdAuthKeyedMD5, BfdAuthKeyedSHA1, BfdAuthSimplePassword, BfdInterval};
|
||||||
|
|
||||||
pub const DEFAULT_CONFIG_PATH: &str = "/etc/rust-bfd/config.yaml";
|
pub const DEFAULT_CONFIG_PATH: &str = "/etc/rust-bfd/config.yaml";
|
||||||
pub const DEFAULT_HOOKS_DIR: &str = "/etc/rust-bfd/hooks.d";
|
pub const DEFAULT_HOOKS_DIR: &str = "/etc/rust-bfd/hooks.d";
|
||||||
@@ -29,6 +30,10 @@ pub const ENV_DEFAULTS_DESIRED_MIN_TX: &str = "BFD_DEFAULTS_DESIRED_MIN_TX";
|
|||||||
pub const ENV_DEFAULTS_REQUIRED_MIN_RX: &str = "BFD_DEFAULTS_REQUIRED_MIN_RX";
|
pub const ENV_DEFAULTS_REQUIRED_MIN_RX: &str = "BFD_DEFAULTS_REQUIRED_MIN_RX";
|
||||||
pub const ENV_DEFAULTS_DETECT_MULT: &str = "BFD_DEFAULTS_DETECT_MULT";
|
pub const ENV_DEFAULTS_DETECT_MULT: &str = "BFD_DEFAULTS_DETECT_MULT";
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
Io {
|
Io {
|
||||||
@@ -94,6 +99,71 @@ impl Default for Defaults {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Validate)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct PasswordAuthConfig {
|
||||||
|
pub key_id: u8,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
// The password is a binary string, and MUST be 1 to 16 bytes in
|
||||||
|
// length.
|
||||||
|
#[validate(min_items = 1)]
|
||||||
|
#[validate(max_items = 16)]
|
||||||
|
pub password: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Validate)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Md5AuthConfig {
|
||||||
|
pub key_id: u8,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
// The key is given verbatim (not derived) and must be exactly 16 bytes
|
||||||
|
pub key: [u8; 16],
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub meticulous: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Validate)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Sha1AuthConfig {
|
||||||
|
pub key_id: u8,
|
||||||
|
#[serde(with = "serde_bytes")]
|
||||||
|
// The key is given verbatim (not derived) and must be exactly 20 bytes
|
||||||
|
pub key: [u8; 20],
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub meticulous: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields, tag = "type", rename_all = "lowercase")]
|
||||||
|
pub enum AuthConfig {
|
||||||
|
Password(PasswordAuthConfig),
|
||||||
|
Md5(Md5AuthConfig),
|
||||||
|
Sha1(Sha1AuthConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthConfig> for BfdAuth {
|
||||||
|
fn from(value: AuthConfig) -> Self {
|
||||||
|
match value {
|
||||||
|
AuthConfig::Password(ac) => BfdAuth::SimplePassword(BfdAuthSimplePassword {
|
||||||
|
key_id: ac.key_id,
|
||||||
|
password: ac.password,
|
||||||
|
}),
|
||||||
|
AuthConfig::Md5(ac) => BfdAuth::KeyedMD5(BfdAuthKeyedMD5 {
|
||||||
|
key_id: ac.key_id,
|
||||||
|
meticulous: ac.meticulous,
|
||||||
|
digest: ac.key,
|
||||||
|
seq: 0, // set during signing
|
||||||
|
}),
|
||||||
|
AuthConfig::Sha1(ac) => BfdAuth::KeyedSHA1(BfdAuthKeyedSHA1 {
|
||||||
|
key_id: ac.key_id,
|
||||||
|
meticulous: ac.meticulous,
|
||||||
|
digest: ac.key,
|
||||||
|
seq: 0, // set during signing
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct PeerConfig {
|
pub struct PeerConfig {
|
||||||
@@ -112,6 +182,8 @@ pub struct PeerConfig {
|
|||||||
pub required_min_rx: Option<Duration>,
|
pub required_min_rx: Option<Duration>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub detect_mult: Option<u8>,
|
pub detect_mult: Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub auth: Option<AuthConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PeerConfig {
|
impl PeerConfig {
|
||||||
@@ -121,6 +193,7 @@ impl PeerConfig {
|
|||||||
desired_min_tx: None,
|
desired_min_tx: None,
|
||||||
required_min_rx: None,
|
required_min_rx: None,
|
||||||
detect_mult: None,
|
detect_mult: None,
|
||||||
|
auth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +326,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validate and apply defaults to each peer.
|
/// Validate and apply defaults to each peer.
|
||||||
pub fn resolve(&self) -> Result<ResolvedConfig, ConfigError> {
|
pub fn resolve(self) -> Result<ResolvedConfig, ConfigError> {
|
||||||
let invalid = |message: String| ConfigError::Invalid(message);
|
let invalid = |message: String| ConfigError::Invalid(message);
|
||||||
|
|
||||||
if self.bind.is_empty() {
|
if self.bind.is_empty() {
|
||||||
@@ -269,7 +342,7 @@ impl Config {
|
|||||||
let mut seen = HashSet::new();
|
let mut seen = HashSet::new();
|
||||||
let peers = self
|
let peers = self
|
||||||
.peers
|
.peers
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|peer| {
|
.map(|peer| {
|
||||||
if !seen.insert(peer.address) {
|
if !seen.insert(peer.address) {
|
||||||
return Err(invalid(format!("duplicate peer {}", peer.address)));
|
return Err(invalid(format!("duplicate peer {}", peer.address)));
|
||||||
@@ -304,6 +377,7 @@ impl Config {
|
|||||||
&what("required_min_rx"),
|
&what("required_min_rx"),
|
||||||
)?,
|
)?,
|
||||||
detect_mult,
|
detect_mult,
|
||||||
|
auth: peer.auth,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
@@ -351,12 +425,13 @@ pub struct ResolvedConfig {
|
|||||||
pub peers: Vec<ResolvedPeer>,
|
pub peers: Vec<ResolvedPeer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ResolvedPeer {
|
pub struct ResolvedPeer {
|
||||||
pub address: IpAddr,
|
pub address: IpAddr,
|
||||||
pub desired_min_tx: BfdInterval,
|
pub desired_min_tx: BfdInterval,
|
||||||
pub required_min_rx: BfdInterval,
|
pub required_min_rx: BfdInterval,
|
||||||
pub detect_mult: u8,
|
pub detect_mult: u8,
|
||||||
|
pub auth: Option<AuthConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -418,7 +493,7 @@ peers:
|
|||||||
#[test]
|
#[test]
|
||||||
fn peers_inherit_defaults_and_override() {
|
fn peers_inherit_defaults_and_override() {
|
||||||
let resolved = Config::from_yaml(FULL).unwrap().resolve().unwrap();
|
let resolved = Config::from_yaml(FULL).unwrap().resolve().unwrap();
|
||||||
let (a, b) = (resolved.peers[0], resolved.peers[1]);
|
let (a, b) = (&resolved.peers[0], &resolved.peers[1]);
|
||||||
assert_eq!(a.desired_min_tx, BfdInterval::from_millis(500));
|
assert_eq!(a.desired_min_tx, BfdInterval::from_millis(500));
|
||||||
assert_eq!(a.required_min_rx, BfdInterval::from_millis(250));
|
assert_eq!(a.required_min_rx, BfdInterval::from_millis(250));
|
||||||
assert_eq!(a.detect_mult, 5);
|
assert_eq!(a.detect_mult, 5);
|
||||||
@@ -500,10 +575,10 @@ peers:
|
|||||||
#[test]
|
#[test]
|
||||||
fn dist_config_is_valid() {
|
fn dist_config_is_valid() {
|
||||||
let config = Config::from_yaml(include_str!("../config.yaml.dist")).unwrap();
|
let config = Config::from_yaml(include_str!("../config.yaml.dist")).unwrap();
|
||||||
let resolved = config.resolve().unwrap();
|
|
||||||
assert_eq!(resolved.peers.len(), 1);
|
|
||||||
assert_eq!(config.defaults, Defaults::default());
|
assert_eq!(config.defaults, Defaults::default());
|
||||||
assert_eq!(config.hooks_dir, PathBuf::from(DEFAULT_HOOKS_DIR));
|
assert_eq!(config.hooks_dir, PathBuf::from(DEFAULT_HOOKS_DIR));
|
||||||
|
let resolved = config.resolve().unwrap();
|
||||||
|
assert_eq!(resolved.peers.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -582,6 +657,7 @@ peers:
|
|||||||
let mut c = base();
|
let mut c = base();
|
||||||
c.peers.push(PeerConfig::new(ip("2001:db8::2")));
|
c.peers.push(PeerConfig::new(ip("2001:db8::2")));
|
||||||
assert!(c
|
assert!(c
|
||||||
|
.clone()
|
||||||
.resolve()
|
.resolve()
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|||||||
+64
-2
@@ -167,6 +167,28 @@ fn fmt_ago(t: Option<Instant>) -> String {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A snapshot of a session's authentication state. Sequence numbers are `None` when not applicable or not yet known.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct AuthStats {
|
||||||
|
pub auth_type: BfdAuthType,
|
||||||
|
pub key_id: u8,
|
||||||
|
pub rcv_seq: Option<u32>,
|
||||||
|
pub xmit_seq: Option<u32>,
|
||||||
|
}
|
||||||
|
impl Display for AuthStats {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let seq = |s: Option<u32>| s.map_or("-".to_string(), |s| s.to_string());
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} KeyId:{} RcvSeq:{} XmitSeq:{}",
|
||||||
|
self.auth_type,
|
||||||
|
self.key_id,
|
||||||
|
seq(self.rcv_seq),
|
||||||
|
seq(self.xmit_seq)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BfdSessionStats {
|
pub struct BfdSessionStats {
|
||||||
pub local_ip: IpAddr,
|
pub local_ip: IpAddr,
|
||||||
@@ -183,8 +205,14 @@ pub struct BfdSessionStats {
|
|||||||
pub last_change: Instant,
|
pub last_change: Instant,
|
||||||
pub detect_time: BfdInterval,
|
pub detect_time: BfdInterval,
|
||||||
pub base_interval: BfdInterval,
|
pub base_interval: BfdInterval,
|
||||||
|
/// `None` when the session does not use authentication
|
||||||
|
pub auth: Option<AuthStats>,
|
||||||
}
|
}
|
||||||
impl BfdSessionStats {
|
impl BfdSessionStats {
|
||||||
|
pub fn with_auth(mut self, auth: Option<AuthStats>) -> Self {
|
||||||
|
self.auth = auth;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn with_base_interval(mut self, base_interval: BfdInterval) -> Self {
|
pub fn with_base_interval(mut self, base_interval: BfdInterval) -> Self {
|
||||||
self.base_interval = base_interval;
|
self.base_interval = base_interval;
|
||||||
self
|
self
|
||||||
@@ -212,6 +240,10 @@ impl Display for BfdSessionStats {
|
|||||||
)?;
|
)?;
|
||||||
writeln!(f, " Rx: {}", self.rx)?;
|
writeln!(f, " Rx: {}", self.rx)?;
|
||||||
writeln!(f, " Tx: {}", self.tx)?;
|
writeln!(f, " Tx: {}", self.tx)?;
|
||||||
|
match &self.auth {
|
||||||
|
Some(auth) => writeln!(f, " Auth: {auth}")?,
|
||||||
|
None => writeln!(f, " Auth: none")?,
|
||||||
|
}
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
" Last Up: {} Last Down: {}",
|
" Last Up: {} Last Down: {}",
|
||||||
@@ -360,6 +392,7 @@ mod tests {
|
|||||||
last_change: Instant::now(),
|
last_change: Instant::now(),
|
||||||
detect_time: BfdInterval::from_millis(900),
|
detect_time: BfdInterval::from_millis(900),
|
||||||
base_interval: BfdInterval::from_secs(1),
|
base_interval: BfdInterval::from_secs(1),
|
||||||
|
auth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +400,7 @@ mod tests {
|
|||||||
fn session_stats_display_never() {
|
fn session_stats_display_never() {
|
||||||
let out = session_stats(None, None).to_string();
|
let out = session_stats(None, None).to_string();
|
||||||
let lines: Vec<&str> = out.lines().collect();
|
let lines: Vec<&str> = out.lines().collect();
|
||||||
assert_eq!(lines.len(), 5);
|
assert_eq!(lines.len(), 6);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines[0].split_whitespace().collect::<Vec<_>>(),
|
lines[0].split_whitespace().collect::<Vec<_>>(),
|
||||||
["192.0.2.2", "Up", "2", "1"]
|
["192.0.2.2", "Up", "2", "1"]
|
||||||
@@ -381,7 +414,36 @@ mod tests {
|
|||||||
" Rx: 2 pkts, interval min/avg/max 300/300/300 ms"
|
" Rx: 2 pkts, interval min/avg/max 300/300/300 ms"
|
||||||
);
|
);
|
||||||
assert_eq!(lines[3], " Tx: 1 pkts, interval min/avg/max -/-/- ms");
|
assert_eq!(lines[3], " Tx: 1 pkts, interval min/avg/max -/-/- ms");
|
||||||
assert_eq!(lines[4], " Last Up: never Last Down: never");
|
assert_eq!(lines[4], " Auth: none");
|
||||||
|
assert_eq!(lines[5], " Last Up: never Last Down: never");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn session_stats_display_auth() {
|
||||||
|
let out = session_stats(None, None)
|
||||||
|
.with_auth(Some(AuthStats {
|
||||||
|
auth_type: BfdAuthType::MetKeyedSHA1,
|
||||||
|
key_id: 3,
|
||||||
|
rcv_seq: Some(100),
|
||||||
|
xmit_seq: Some(7),
|
||||||
|
}))
|
||||||
|
.to_string();
|
||||||
|
assert!(
|
||||||
|
out.contains("\n Auth: meticulous-sha1 KeyId:3 RcvSeq:100 XmitSeq:7\n"),
|
||||||
|
"{out}"
|
||||||
|
);
|
||||||
|
let out = session_stats(None, None)
|
||||||
|
.with_auth(Some(AuthStats {
|
||||||
|
auth_type: BfdAuthType::KeyedMD5,
|
||||||
|
key_id: 1,
|
||||||
|
rcv_seq: None,
|
||||||
|
xmit_seq: Some(7),
|
||||||
|
}))
|
||||||
|
.to_string();
|
||||||
|
assert!(
|
||||||
|
out.contains("\n Auth: md5 KeyId:1 RcvSeq:- XmitSeq:7\n"),
|
||||||
|
"{out}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user