Compare commits
36 commits
main
...
boringtun-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
104f8215ba | ||
|
|
76278809ea | ||
|
|
889ed37f80 | ||
|
|
286ecfa590 | ||
|
|
669eed0dac | ||
|
|
9f84fc6efa | ||
|
|
b60c6ad687 | ||
|
|
1e7750606f | ||
|
|
2b3ef999b9 | ||
|
|
a756630316 | ||
|
|
b922bf56e3 | ||
|
|
5297f61f9f | ||
|
|
d68f36455f | ||
|
|
af09c610b2 | ||
|
|
65efa0a2e1 | ||
|
|
54ec260fe3 | ||
|
|
c346ec5b39 | ||
|
|
3e5a01ffbe | ||
|
|
261f24d9ef | ||
|
|
94233874e6 | ||
|
|
17610ff90d | ||
|
|
2cb9dd75ca | ||
|
|
4a9fab62db | ||
|
|
5226326caa | ||
|
|
5e4491105a | ||
|
|
7f6897f0d6 | ||
|
|
28ebfec3ca | ||
|
|
4038d125db | ||
|
|
6c1c806401 | ||
|
|
02f5a4ef74 | ||
|
|
13a5ab8352 | ||
|
|
c58b77fb3f | ||
|
|
cba9d091fa | ||
|
|
ace35f96ba | ||
|
|
cdc3acdb5e | ||
|
|
6339b6bc4b |
59 changed files with 3805 additions and 521 deletions
4
.github/workflows/build-rust.yml
vendored
4
.github/workflows/build-rust.yml
vendored
|
|
@ -54,6 +54,10 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y ${{ join(matrix.packages, ' ') }}
|
sudo apt-get install -y ${{ join(matrix.packages, ' ') }}
|
||||||
|
- name: Install Windows Deps
|
||||||
|
if: matrix.os == 'windows-2022'
|
||||||
|
shell: bash
|
||||||
|
run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
12
.rustfmt.toml
Normal file
12
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
condense_wildcard_suffixes = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
imports_layout = "HorizontalVertical"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
newline_style = "Unix"
|
||||||
|
overflow_delimited_expr = true
|
||||||
|
reorder_impl_items = true
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
trailing_semicolon = false
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
|
struct_lit_width = 30
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -8,6 +8,9 @@
|
||||||
"editor.acceptSuggestionOnEnter": "on",
|
"editor.acceptSuggestionOnEnter": "on",
|
||||||
"rust-analyzer.restartServerOnConfigChange": true,
|
"rust-analyzer.restartServerOnConfigChange": true,
|
||||||
"rust-analyzer.cargo.features": "all",
|
"rust-analyzer.cargo.features": "all",
|
||||||
|
"rust-analyzer.rustfmt.extraArgs": [
|
||||||
|
"+nightly"
|
||||||
|
],
|
||||||
"[rust]": {
|
"[rust]": {
|
||||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,6 @@
|
||||||
);
|
);
|
||||||
mainGroup = D05B9F6929E39EEC008CB1F9;
|
mainGroup = D05B9F6929E39EEC008CB1F9;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */,
|
|
||||||
);
|
);
|
||||||
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
|
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
{
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "collectionconcurrencykit",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
|
|
||||||
"version" : "0.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "sourcekitten",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
|
||||||
"version" : "0.34.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-argument-parser",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
|
|
||||||
"version" : "1.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-syntax",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-syntax.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "013a48e2312e57b7b355db25bd3ea75282ebf274",
|
|
||||||
"version" : "0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swiftlint",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/realm/SwiftLint.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "eb85125a5f293de3d3248af259980c98bc2b1faa",
|
|
||||||
"version" : "0.51.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swiftytexttable",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
|
|
||||||
"version" : "0.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swxmlhash",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/drmohundro/SWXMLHash.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
|
|
||||||
"version" : "7.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "yams",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/jpsim/Yams.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
|
|
||||||
"version" : "5.0.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 2
|
|
||||||
}
|
|
||||||
|
|
@ -113,7 +113,7 @@ final class BurrowIpc {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func request<U: Decodable>(_ request: Request, type: U.Type) async throws -> U {
|
func request<U: Decodable>(_ request: any Request, type: U.Type) async throws -> U {
|
||||||
do {
|
do {
|
||||||
var data: Data = try JSONEncoder().encode(request)
|
var data: Data = try JSONEncoder().encode(request)
|
||||||
data.append(contentsOf: [10])
|
data.append(contentsOf: [10])
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,43 @@ enum BurrowError: Error {
|
||||||
case resultIsNone
|
case resultIsNone
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol Request: Codable {
|
protocol Request: Codable where CommandT: Codable {
|
||||||
|
associatedtype CommandT
|
||||||
var id: UInt { get set }
|
var id: UInt { get set }
|
||||||
var command: String { get set }
|
var command: CommandT { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BurrowRequest: Request {
|
struct BurrowSingleCommand: Request {
|
||||||
var id: UInt
|
var id: UInt
|
||||||
var command: String
|
var command: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BurrowRequest<T>: Request where T: Codable {
|
||||||
|
var id: UInt
|
||||||
|
var command: T
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BurrowStartRequest: Codable {
|
||||||
|
struct TunOptions: Codable {
|
||||||
|
let name: String?
|
||||||
|
let no_pi: Bool
|
||||||
|
let tun_excl: Bool
|
||||||
|
let tun_retrieve: Bool
|
||||||
|
let address: String?
|
||||||
|
}
|
||||||
|
struct StartOptions: Codable {
|
||||||
|
let tun: TunOptions
|
||||||
|
}
|
||||||
|
let Start: StartOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func start_req_fd(id: UInt) -> BurrowRequest<BurrowStartRequest> {
|
||||||
|
let command = BurrowStartRequest(Start: BurrowStartRequest.StartOptions(
|
||||||
|
tun: BurrowStartRequest.TunOptions(name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil)
|
||||||
|
))
|
||||||
|
return BurrowRequest(id: id, command: command)
|
||||||
|
}
|
||||||
|
|
||||||
struct Response<T>: Decodable where T: Decodable {
|
struct Response<T>: Decodable where T: Decodable {
|
||||||
var id: UInt
|
var id: UInt
|
||||||
var result: T
|
var result: T
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.developer.networking.networkextension</key>
|
<key>com.apple.developer.networking.networkextension</key>
|
||||||
<array>
|
<array>
|
||||||
<string>packet-tunnel-provider</string>
|
<string>packet-tunnel-provider</string>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
let logger = Logger(subsystem: "com.hackclub.burrow", category: "frontend")
|
let logger = Logger(subsystem: "com.hackclub.burrow", category: "frontend")
|
||||||
var client: BurrowIpc?
|
var client: BurrowIpc?
|
||||||
var osInitialized = false
|
var osInitialized = false
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
||||||
logger.log("Starting tunnel")
|
logger.log("Starting tunnel")
|
||||||
if !osInitialized {
|
if !osInitialized {
|
||||||
libburrow.initialize_oslog()
|
libburrow.initialize_oslog()
|
||||||
|
|
@ -15,9 +15,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
libburrow.start_srv()
|
libburrow.start_srv()
|
||||||
client = BurrowIpc(logger: logger)
|
client = BurrowIpc(logger: logger)
|
||||||
logger.info("Started server")
|
logger.info("Started server")
|
||||||
Task {
|
|
||||||
do {
|
do {
|
||||||
let command = BurrowRequest(id: 0, command: "ServerConfig")
|
let command = BurrowSingleCommand(id: 0, command: "ServerConfig")
|
||||||
guard let data = try await client?.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
|
guard let data = try await client?.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
|
||||||
else {
|
else {
|
||||||
throw BurrowError.cantParseResult
|
throw BurrowError.cantParseResult
|
||||||
|
|
@ -32,11 +31,19 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
try await self.setTunnelNetworkSettings(tunNs)
|
try await self.setTunnelNetworkSettings(tunNs)
|
||||||
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)")
|
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)")
|
||||||
completionHandler(nil)
|
|
||||||
|
// let tunFd = self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int;
|
||||||
|
// self.logger.info("Found File Descriptor: \(tunFd)")
|
||||||
|
let startCommand = start_req_fd(id: 1)
|
||||||
|
guard let data = try await client?.request(startCommand, type: Response<BurrowResult<String>>.self)
|
||||||
|
else {
|
||||||
|
throw BurrowError.cantParseResult
|
||||||
|
}
|
||||||
|
let encodedStartRes = try JSONEncoder().encode(data.result)
|
||||||
|
self.logger.log("Received start server response: \(String(decoding: encodedStartRes, as: UTF8.self))")
|
||||||
} catch {
|
} catch {
|
||||||
self.logger.error("An error occurred: \(error)")
|
self.logger.error("An error occurred: \(error)")
|
||||||
completionHandler(error)
|
throw error
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
|
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
|
||||||
|
|
@ -50,16 +57,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
|
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
|
||||||
return nst
|
return nst
|
||||||
}
|
}
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with reason: NEProviderStopReason) async {
|
||||||
completionHandler()
|
|
||||||
}
|
}
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
override func handleAppMessage(_ messageData: Data) async -> Data? {
|
||||||
if let handler = completionHandler {
|
messageData
|
||||||
handler(messageData)
|
|
||||||
}
|
}
|
||||||
}
|
override func sleep() async {
|
||||||
override func sleep(completionHandler: @escaping () -> Void) {
|
|
||||||
completionHandler()
|
|
||||||
}
|
}
|
||||||
override func wake() {
|
override func wake() {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
413
Cargo.lock
generated
413
Cargo.lock
generated
|
|
@ -17,6 +17,16 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aes"
|
name = "aes"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -39,15 +49,16 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.5.0"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
"anstyle-query",
|
"anstyle-query",
|
||||||
"anstyle-wincon",
|
"anstyle-wincon",
|
||||||
"colorchoice",
|
"colorchoice",
|
||||||
|
"is-terminal",
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -77,9 +88,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-wincon"
|
name = "anstyle-wincon"
|
||||||
version = "2.1.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
|
@ -92,14 +103,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "arrayvec"
|
||||||
version = "1.9.0"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"event-listener",
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -125,9 +144,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.2"
|
version = "0.21.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
|
|
@ -176,7 +195,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
"which",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -192,6 +211,15 @@ version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
|
@ -211,15 +239,30 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||||
name = "burrow"
|
name = "burrow"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aead",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
"base64",
|
||||||
|
"blake2",
|
||||||
"caps",
|
"caps",
|
||||||
|
"chacha20poly1305",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"etherparse",
|
||||||
|
"fehler",
|
||||||
|
"futures",
|
||||||
|
"hmac",
|
||||||
"insta",
|
"insta",
|
||||||
|
"ip_network",
|
||||||
|
"ip_network_table",
|
||||||
|
"ipnet",
|
||||||
"libsystemd",
|
"libsystemd",
|
||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
|
"parking_lot",
|
||||||
|
"rand",
|
||||||
|
"rand_core",
|
||||||
|
"ring",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -230,6 +273,8 @@ dependencies = [
|
||||||
"tracing-oslog",
|
"tracing-oslog",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tun",
|
"tun",
|
||||||
|
"uuid",
|
||||||
|
"x25519-dalek",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -277,11 +322,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.79"
|
version = "1.0.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -299,6 +345,30 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -307,6 +377,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
"inout",
|
"inout",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -322,19 +393,20 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.4"
|
version = "4.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
|
checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.4.4"
|
version = "4.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
|
checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -344,14 +416,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.4.2"
|
version = "4.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -368,9 +440,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
|
checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
@ -443,9 +515,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "4.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"curve25519-dalek-derive",
|
||||||
|
"fiat-crypto",
|
||||||
|
"platforms",
|
||||||
|
"rustc_version",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek-derive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.39",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -459,9 +559,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dyn-clone"
|
name = "dyn-clone"
|
||||||
version = "1.0.14"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd"
|
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
|
|
@ -519,10 +619,34 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "etherparse"
|
||||||
version = "2.5.3"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
checksum = "bcb08c4aab4e2985045305551e67126b43f1b6b136bc4e1cd87fb0327877a611"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
|
|
@ -553,6 +677,12 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fiat-crypto"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.26"
|
version = "1.0.26"
|
||||||
|
|
@ -649,7 +779,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -692,6 +822,17 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.27.3"
|
version = "0.27.3"
|
||||||
|
|
@ -864,9 +1005,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.32.0"
|
version = "1.34.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
|
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
|
@ -896,11 +1037,36 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ip_network"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ip_network_table"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0"
|
||||||
|
dependencies = [
|
||||||
|
"ip_network",
|
||||||
|
"ip_network_table-deps-treebitmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ip_network_table-deps-treebitmap"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
|
|
@ -951,9 +1117,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
|
|
@ -1061,7 +1227,7 @@ checksum = "4901771e1d44ddb37964565c654a3223ba41a594d02b8da471cc4464912b5cfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1173,6 +1339,12 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.55"
|
version = "0.10.55"
|
||||||
|
|
@ -1196,7 +1368,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1223,6 +1395,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -1283,9 +1461,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.9"
|
version = "0.2.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-utils"
|
name = "pin-utils"
|
||||||
|
|
@ -1299,6 +1477,29 @@ version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platforms"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
|
@ -1306,32 +1507,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
|
checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.63"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.29"
|
version = "1.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
|
|
@ -1411,6 +1636,20 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
|
|
@ -1423,6 +1662,15 @@ 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 = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.37.21"
|
version = "0.37.21"
|
||||||
|
|
@ -1518,6 +1766,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.164"
|
version = "1.0.164"
|
||||||
|
|
@ -1535,7 +1789,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1622,9 +1876,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
|
checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
|
|
@ -1651,6 +1905,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ssri"
|
name = "ssri"
|
||||||
version = "9.0.0"
|
version = "9.0.0"
|
||||||
|
|
@ -1698,9 +1958,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.22"
|
version = "2.0.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1747,7 +2007,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1817,7 +2077,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1870,7 +2130,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2004,6 +2264,22 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
|
@ -2023,10 +2299,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.4.0"
|
version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
|
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2084,7 +2361,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2118,7 +2395,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.22",
|
"syn 2.0.39",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
@ -2352,6 +2629,18 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x25519-dalek"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek",
|
||||||
|
"rand_core",
|
||||||
|
"serde",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xxhash-rust"
|
name = "xxhash-rust"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
|
@ -2367,6 +2656,26 @@ dependencies = [
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.39",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "0.6.6"
|
version = "0.6.6"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["burrow", "tun"]
|
members = ["burrow", "tun"]
|
||||||
|
resolver = "2"
|
||||||
exclude = ["burrow-gtk"]
|
exclude = ["burrow-gtk"]
|
||||||
|
|
|
||||||
18
Makefile
Normal file
18
Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
tun_num := $(shell ifconfig | awk -F 'utun|[: ]' '/utun[0-9]/ {print $$2}' | tail -n 1)
|
||||||
|
|
||||||
|
check:
|
||||||
|
@cargo check
|
||||||
|
|
||||||
|
build:
|
||||||
|
@cargo run build
|
||||||
|
|
||||||
|
daemon:
|
||||||
|
@RUST_BACKTRACE=1 RUST_LOG=debug cargo run daemon
|
||||||
|
|
||||||
|
start:
|
||||||
|
@RUST_BACKTRACE=1 RUST_LOG=debug cargo run start
|
||||||
|
|
||||||
|
test-dns:
|
||||||
|
@sudo route delete 8.8.8.8
|
||||||
|
@sudo route add 8.8.8.8 -interface utun$(tun_num)
|
||||||
|
@dig @8.8.8.8 hackclub.com
|
||||||
|
|
@ -10,7 +10,7 @@ crate-type = ["lib", "staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread"] }
|
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread", "time"] }
|
||||||
tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
|
tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
|
||||||
clap = { version = "4.3.2", features = ["derive"] }
|
clap = { version = "4.3.2", features = ["derive"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
@ -22,8 +22,24 @@ env_logger = "0.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
async-channel = "1.9"
|
blake2 = "0.10.6"
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rand_core = "0.6.4"
|
||||||
|
aead = "0.5.2"
|
||||||
|
x25519-dalek = { version = "2.0.0", features = ["reusable_secrets", "static_secrets"] }
|
||||||
|
ring = "0.17.7"
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
hmac = "0.12"
|
||||||
|
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||||
|
base64 = "0.21.4"
|
||||||
|
fehler = "1.0.0"
|
||||||
|
ip_network_table = "0.2.0"
|
||||||
|
ip_network = "0.4.0"
|
||||||
|
async-channel = "2.1.1"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
futures = "0.3.28"
|
||||||
|
uuid = { version = "1.6.1", features = ["v4"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
caps = "0.5.5"
|
caps = "0.5.5"
|
||||||
|
|
@ -34,6 +50,7 @@ nix = { version = "0.26.2" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = { version = "1.32.0", features = ["yaml"] }
|
insta = { version = "1.32.0", features = ["yaml"] }
|
||||||
|
etherparse = "0.12"
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
assets = [
|
assets = [
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
use tracing::{debug, Subscriber};
|
use tracing::debug;
|
||||||
use tracing::instrument::WithSubscriber;
|
|
||||||
use tracing_oslog::OsLogger;
|
use tracing_oslog::OsLogger;
|
||||||
use tracing_subscriber::FmtSubscriber;
|
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
|
||||||
pub use crate::daemon::start_srv;
|
pub use crate::daemon::start_srv;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn initialize_oslog() {
|
pub extern "C" fn initialize_oslog() {
|
||||||
let collector = tracing_subscriber::registry()
|
let collector =
|
||||||
.with(OsLogger::new("com.hackclub.burrow", "backend"));
|
tracing_subscriber::registry().with(OsLogger::new("com.hackclub.burrow", "backend"));
|
||||||
tracing::subscriber::set_global_default(collector).unwrap();
|
tracing::subscriber::set_global_default(collector).unwrap();
|
||||||
debug!("Initialized oslog tracing in libburrow rust FFI");
|
debug!("Initialized oslog tracing in libburrow rust FFI");
|
||||||
}
|
}
|
||||||
|
|
@ -12,21 +12,22 @@ pub enum DaemonCommand {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DaemonStartOptions {
|
pub struct DaemonStartOptions {
|
||||||
pub(super) tun: TunOptions,
|
pub tun: TunOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_daemoncommand_serialization() {
|
fn test_daemoncommand_serialization() {
|
||||||
|
insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start(
|
||||||
|
DaemonStartOptions::default()
|
||||||
|
))
|
||||||
|
.unwrap());
|
||||||
insta::assert_snapshot!(
|
insta::assert_snapshot!(
|
||||||
serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()
|
serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {
|
||||||
|
tun: TunOptions { ..TunOptions::default() }
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
);
|
);
|
||||||
insta::assert_snapshot!(
|
insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::ServerInfo).unwrap());
|
||||||
serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()
|
insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Stop).unwrap());
|
||||||
);
|
insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::ServerConfig).unwrap())
|
||||||
insta::assert_snapshot!(
|
|
||||||
serde_json::to_string(&DaemonCommand::Stop).unwrap()
|
|
||||||
);
|
|
||||||
insta::assert_snapshot!(
|
|
||||||
serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,43 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use tokio::{sync::RwLock, task::JoinHandle};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
use DaemonResponse;
|
use tun::tokio::TunInterface;
|
||||||
use crate::daemon::response::{DaemonResponseData, ServerConfig, ServerInfo};
|
|
||||||
use super::*;
|
use crate::{
|
||||||
|
daemon::{
|
||||||
|
command::DaemonCommand,
|
||||||
|
response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo},
|
||||||
|
},
|
||||||
|
wireguard::Interface,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RunState {
|
||||||
|
Running(JoinHandle<Result<()>>),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DaemonInstance {
|
pub struct DaemonInstance {
|
||||||
rx: async_channel::Receiver<DaemonCommand>,
|
rx: async_channel::Receiver<DaemonCommand>,
|
||||||
sx: async_channel::Sender<DaemonResponse>,
|
sx: async_channel::Sender<DaemonResponse>,
|
||||||
tun_interface: Option<TunInterface>,
|
tun_interface: Option<Arc<RwLock<TunInterface>>>,
|
||||||
|
wg_interface: Arc<RwLock<Interface>>,
|
||||||
|
wg_state: RunState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonInstance {
|
impl DaemonInstance {
|
||||||
pub fn new(rx: async_channel::Receiver<DaemonCommand>, sx: async_channel::Sender<DaemonResponse>) -> Self {
|
pub fn new(
|
||||||
|
rx: async_channel::Receiver<DaemonCommand>,
|
||||||
|
sx: async_channel::Sender<DaemonResponse>,
|
||||||
|
wg_interface: Arc<RwLock<Interface>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rx,
|
rx,
|
||||||
sx,
|
sx,
|
||||||
|
wg_interface,
|
||||||
tun_interface: None,
|
tun_interface: None,
|
||||||
|
wg_state: RunState::Idle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,28 +45,52 @@ impl DaemonInstance {
|
||||||
info!("Daemon got command: {:?}", command);
|
info!("Daemon got command: {:?}", command);
|
||||||
match command {
|
match command {
|
||||||
DaemonCommand::Start(st) => {
|
DaemonCommand::Start(st) => {
|
||||||
if self.tun_interface.is_none() {
|
match self.wg_state {
|
||||||
debug!("Daemon attempting start tun interface.");
|
RunState::Running(_) => {
|
||||||
self.tun_interface = Some(st.tun.open()?);
|
|
||||||
info!("Daemon started tun interface");
|
|
||||||
} else {
|
|
||||||
warn!("Got start, but tun interface already up.");
|
warn!("Got start, but tun interface already up.");
|
||||||
}
|
}
|
||||||
|
RunState::Idle => {
|
||||||
|
let tun_if = Arc::new(RwLock::new(st.tun.open()?));
|
||||||
|
|
||||||
|
debug!("Setting tun_interface");
|
||||||
|
self.tun_interface = Some(tun_if.clone());
|
||||||
|
debug!("tun_interface set: {:?}", self.tun_interface);
|
||||||
|
|
||||||
|
debug!("Setting tun on wg_interface");
|
||||||
|
self.wg_interface.write().await.set_tun(tun_if);
|
||||||
|
debug!("tun set on wg_interface");
|
||||||
|
|
||||||
|
debug!("Cloning wg_interface");
|
||||||
|
let tmp_wg = self.wg_interface.clone();
|
||||||
|
debug!("wg_interface cloned");
|
||||||
|
|
||||||
|
debug!("Spawning run task");
|
||||||
|
let run_task = tokio::spawn(async move {
|
||||||
|
debug!("Running wg_interface");
|
||||||
|
let twlock = tmp_wg.read().await;
|
||||||
|
debug!("wg_interface read lock acquired");
|
||||||
|
twlock.run().await
|
||||||
|
});
|
||||||
|
debug!("Run task spawned: {:?}", run_task);
|
||||||
|
|
||||||
|
debug!("Setting wg_state to Running");
|
||||||
|
self.wg_state = RunState::Running(run_task);
|
||||||
|
debug!("wg_state set to Running");
|
||||||
|
|
||||||
|
info!("Daemon started tun interface");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(DaemonResponseData::None)
|
Ok(DaemonResponseData::None)
|
||||||
}
|
}
|
||||||
DaemonCommand::ServerInfo => {
|
DaemonCommand::ServerInfo => match &self.tun_interface {
|
||||||
match &self.tun_interface {
|
None => Ok(DaemonResponseData::None),
|
||||||
None => {Ok(DaemonResponseData::None)}
|
|
||||||
Some(ti) => {
|
Some(ti) => {
|
||||||
info!("{:?}", ti);
|
info!("{:?}", ti);
|
||||||
Ok(
|
Ok(DaemonResponseData::ServerInfo(ServerInfo::try_from(
|
||||||
DaemonResponseData::ServerInfo(
|
ti.read().await.inner.get_ref(),
|
||||||
ServerInfo::try_from(ti)?
|
)?))
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
DaemonCommand::Stop => {
|
DaemonCommand::Stop => {
|
||||||
if self.tun_interface.is_some() {
|
if self.tun_interface.is_some() {
|
||||||
self.tun_interface = None;
|
self.tun_interface = None;
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,51 @@
|
||||||
use super::*;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod net;
|
mod net;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
use instance::DaemonInstance;
|
use anyhow::Result;
|
||||||
use net::listen;
|
|
||||||
|
|
||||||
pub use command::{DaemonCommand, DaemonStartOptions};
|
pub use command::{DaemonCommand, DaemonStartOptions};
|
||||||
pub use net::DaemonClient;
|
use instance::DaemonInstance;
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
pub use net::start_srv;
|
pub use net::start_srv;
|
||||||
|
pub use net::DaemonClient;
|
||||||
|
pub use response::{DaemonResponse, DaemonResponseData, ServerInfo};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
pub use response::{DaemonResponseData, DaemonResponse, ServerInfo};
|
use crate::{
|
||||||
|
daemon::net::listen,
|
||||||
|
wireguard::{Config, Interface},
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn daemon_main() -> Result<()> {
|
pub async fn daemon_main() -> Result<()> {
|
||||||
let (commands_tx, commands_rx) = async_channel::unbounded();
|
let (commands_tx, commands_rx) = async_channel::unbounded();
|
||||||
let (response_tx, response_rx) = async_channel::unbounded();
|
let (response_tx, response_rx) = async_channel::unbounded();
|
||||||
let mut inst = DaemonInstance::new(commands_rx, response_tx);
|
|
||||||
|
|
||||||
tokio::try_join!(inst.run(), listen(commands_tx, response_rx)).map(|_| ())
|
let config = Config::default();
|
||||||
|
let iface: Interface = config.try_into()?;
|
||||||
|
|
||||||
|
let mut inst: DaemonInstance =
|
||||||
|
DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface)));
|
||||||
|
|
||||||
|
tracing::info!("Starting daemon jobs...");
|
||||||
|
|
||||||
|
let inst_job = tokio::spawn(async move {
|
||||||
|
let res = inst.run().await;
|
||||||
|
if let Err(e) = res {
|
||||||
|
tracing::error!("Error when running instance: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let listen_job = tokio::spawn(async move {
|
||||||
|
let res = listen(commands_tx, response_rx).await;
|
||||||
|
if let Err(e) = res {
|
||||||
|
tracing::error!("Error when listening: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::try_join!(inst_job, listen_job)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tracing::error;
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::daemon::{daemon_main, DaemonClient};
|
use crate::daemon::{daemon_main, DaemonClient};
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn start_srv(){
|
pub extern "C" fn start_srv() {
|
||||||
|
info!("Starting server");
|
||||||
let _handle = thread::spawn(move || {
|
let _handle = thread::spawn(move || {
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
|
@ -16,9 +19,13 @@ pub extern "C" fn start_srv(){
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
loop {
|
loop {
|
||||||
if let Ok(_) = DaemonClient::new().await{
|
match DaemonClient::new().await {
|
||||||
|
Ok(..) => {
|
||||||
|
info!("Server successfully started");
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
Err(e) => error!("Could not connect to server: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use super::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::DaemonCommand;
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
mod unix;
|
mod unix;
|
||||||
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
||||||
|
|
@ -28,4 +29,3 @@ pub struct DaemonRequest {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub command: DaemonCommand,
|
pub command: DaemonCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
use super::*;
|
|
||||||
use std::os::fd::IntoRawFd;
|
use std::os::fd::IntoRawFd;
|
||||||
|
|
||||||
pub async fn listen(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
use anyhow::Result;
|
||||||
if !libsystemd::daemon::booted() || listen_with_systemd(cmd_tx.clone(), rsp_rx.clone()).await.is_err() {
|
|
||||||
|
use super::*;
|
||||||
|
use crate::daemon::DaemonResponse;
|
||||||
|
|
||||||
|
pub async fn listen(
|
||||||
|
cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||||
|
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !libsystemd::daemon::booted()
|
||||||
|
|| listen_with_systemd(cmd_tx.clone(), rsp_rx.clone())
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
unix::listen(cmd_tx, rsp_rx).await?;
|
unix::listen(cmd_tx, rsp_rx).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn listen_with_systemd(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
async fn listen_with_systemd(
|
||||||
|
cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||||
|
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||||
|
) -> Result<()> {
|
||||||
let fds = libsystemd::activation::receive_descriptors(false)?;
|
let fds = libsystemd::activation::receive_descriptors(false)?;
|
||||||
super::unix::listen_with_optional_fd(cmd_tx, rsp_rx,Some(fds[0].clone().into_raw_fd())).await
|
super::unix::listen_with_optional_fd(cmd_tx, rsp_rx, Some(fds[0].clone().into_raw_fd())).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DaemonClient = unix::DaemonClient;
|
pub type DaemonClient = unix::DaemonClient;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
use super::*;
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use log::log;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{
|
use std::{
|
||||||
ascii, io,
|
io,
|
||||||
os::fd::{FromRawFd, RawFd},
|
os::{
|
||||||
os::unix::net::UnixListener as StdUnixListener,
|
fd::{FromRawFd, RawFd},
|
||||||
path::Path,
|
unix::net::UnixListener as StdUnixListener,
|
||||||
|
},
|
||||||
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||||
net::{UnixListener, UnixStream},
|
net::{UnixListener, UnixStream},
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::{debug, info};
|
||||||
use tracing::info;
|
|
||||||
|
use super::*;
|
||||||
|
use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData};
|
||||||
|
|
||||||
#[cfg(not(target_vendor = "apple"))]
|
#[cfg(not(target_vendor = "apple"))]
|
||||||
const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
|
const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
|
||||||
|
|
@ -35,7 +36,7 @@ fn fetch_socket_path() -> Option<PathBuf> {
|
||||||
for path in tries {
|
for path in tries {
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Some(path);
|
return Some(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
@ -86,7 +87,8 @@ pub(crate) async fn listen_with_optional_fd(
|
||||||
let cmd_tx = cmd_tx.clone();
|
let cmd_tx = cmd_tx.clone();
|
||||||
|
|
||||||
// I'm pretty sure we won't need to manually join / shut this down,
|
// I'm pretty sure we won't need to manually join / shut this down,
|
||||||
// `lines` will return Err during dropping, and this task should exit gracefully.
|
// `lines` will return Err during dropping, and this task should exit
|
||||||
|
// gracefully.
|
||||||
let rsp_rxc = rsp_rx.clone();
|
let rsp_rxc = rsp_rx.clone();
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
let cmd_tx = cmd_tx;
|
let cmd_tx = cmd_tx;
|
||||||
|
|
@ -102,6 +104,7 @@ pub(crate) async fn listen_with_optional_fd(
|
||||||
Ok(req) => Some(req),
|
Ok(req) => Some(req),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
res.result = Err(e.to_string());
|
res.result = Err(e.to_string());
|
||||||
|
tracing::error!("Failed to parse request: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -115,6 +118,8 @@ pub(crate) async fn listen_with_optional_fd(
|
||||||
retres.push('\n');
|
retres.push('\n');
|
||||||
info!("Sending response: {}", retres);
|
info!("Sending response: {}", retres);
|
||||||
write_stream.write_all(retres.as_bytes()).await.unwrap();
|
write_stream.write_all(retres.as_bytes()).await.unwrap();
|
||||||
|
} else {
|
||||||
|
write_stream.write_all(res.as_bytes()).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
use super::*;
|
use anyhow::Result;
|
||||||
|
|
||||||
pub async fn listen(_cmd_tx: async_channel::Sender<DaemonCommand>, _rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
use super::*;
|
||||||
|
use crate::daemon::DaemonResponse;
|
||||||
|
|
||||||
|
pub async fn listen(
|
||||||
|
_cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||||
|
_rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||||
|
) -> Result<()> {
|
||||||
unimplemented!("This platform does not currently support daemon mode.")
|
unimplemented!("This platform does not currently support daemon mode.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use anyhow::anyhow;
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tun::TunInterface;
|
use tun::TunInterface;
|
||||||
|
|
@ -7,55 +6,50 @@ use tun::TunInterface;
|
||||||
pub struct DaemonResponse {
|
pub struct DaemonResponse {
|
||||||
// Error types can't be serialized, so this is the second best option.
|
// Error types can't be serialized, so this is the second best option.
|
||||||
pub result: Result<DaemonResponseData, String>,
|
pub result: Result<DaemonResponseData, String>,
|
||||||
pub id: u32
|
pub id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonResponse{
|
impl DaemonResponse {
|
||||||
pub fn new(result: Result<DaemonResponseData, impl ToString>) -> Self{
|
pub fn new(result: Result<DaemonResponseData, impl ToString>) -> Self {
|
||||||
Self{
|
|
||||||
result: result.map_err(|e| e.to_string()),
|
|
||||||
id: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<DaemonResponse> for DaemonResponseData{
|
|
||||||
fn into(self) -> DaemonResponse{
|
|
||||||
DaemonResponse::new(Ok::<DaemonResponseData, String>(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DaemonResponse{
|
|
||||||
pub fn with_id(self, id: u32) -> Self{
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
result: result.map_err(|e| e.to_string()),
|
||||||
..self
|
id: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DaemonResponseData> for DaemonResponse {
|
||||||
|
fn from(val: DaemonResponseData) -> Self {
|
||||||
|
DaemonResponse::new(Ok::<DaemonResponseData, String>(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaemonResponse {
|
||||||
|
pub fn with_id(self, id: u32) -> Self {
|
||||||
|
Self { id, ..self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub ip: Option<String>,
|
pub ip: Option<String>,
|
||||||
pub mtu: Option<i32>
|
pub mtu: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&TunInterface> for ServerInfo{
|
impl TryFrom<&TunInterface> for ServerInfo {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
#[cfg(any(target_os="linux",target_vendor="apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
||||||
Ok(
|
Ok(ServerInfo {
|
||||||
ServerInfo{
|
|
||||||
name: server.name().ok(),
|
name: server.name().ok(),
|
||||||
ip: server.ipv4_addr().ok().map(|ip| ip.to_string()),
|
ip: server.ipv4_addr().ok().map(|ip| ip.to_string()),
|
||||||
mtu: server.mtu().ok()
|
mtu: server.mtu().ok(),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os="linux",target_vendor="apple")))]
|
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
||||||
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
||||||
Err(anyhow!("Not implemented in this platform"))
|
Err(anyhow!("Not implemented in this platform"))
|
||||||
}
|
}
|
||||||
|
|
@ -65,45 +59,55 @@ impl TryFrom<&TunInterface> for ServerInfo{
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub address: Option<String>,
|
pub address: Option<String>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub mtu: Option<i32>
|
pub mtu: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerConfig {
|
impl Default for ServerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self{
|
Self {
|
||||||
address: Some("10.0.0.1".to_string()), // Dummy remote address
|
address: Some("10.13.13.2".to_string()), // Dummy remote address
|
||||||
name: None,
|
name: None,
|
||||||
mtu: None
|
mtu: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub enum DaemonResponseData{
|
pub enum DaemonResponseData {
|
||||||
ServerInfo(ServerInfo),
|
ServerInfo(ServerInfo),
|
||||||
ServerConfig(ServerConfig),
|
ServerConfig(ServerConfig),
|
||||||
None
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_response_serialization() -> anyhow::Result<()>{
|
fn test_response_serialization() -> anyhow::Result<()> {
|
||||||
insta::assert_snapshot!(
|
insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::<
|
||||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::None)))?
|
DaemonResponseData,
|
||||||
);
|
String,
|
||||||
insta::assert_snapshot!(
|
>(
|
||||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerInfo(ServerInfo{
|
DaemonResponseData::None
|
||||||
|
)))?);
|
||||||
|
insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::<
|
||||||
|
DaemonResponseData,
|
||||||
|
String,
|
||||||
|
>(
|
||||||
|
DaemonResponseData::ServerInfo(ServerInfo {
|
||||||
name: Some("burrow".to_string()),
|
name: Some("burrow".to_string()),
|
||||||
ip: None,
|
ip: None,
|
||||||
mtu: Some(1500)
|
mtu: Some(1500)
|
||||||
}))))?
|
})
|
||||||
);
|
)))?);
|
||||||
insta::assert_snapshot!(
|
insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Err::<
|
||||||
serde_json::to_string(&DaemonResponse::new(Err::<DaemonResponseData, String>("error".to_string())))?
|
DaemonResponseData,
|
||||||
);
|
String,
|
||||||
insta::assert_snapshot!(
|
>(
|
||||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerConfig(
|
"error".to_string()
|
||||||
ServerConfig::default()
|
)))?);
|
||||||
))))?
|
insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::<
|
||||||
);
|
DaemonResponseData,
|
||||||
|
String,
|
||||||
|
>(
|
||||||
|
DaemonResponseData::ServerConfig(ServerConfig::default())
|
||||||
|
)))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: burrow/src/daemon/command.rs
|
source: burrow/src/daemon/command.rs
|
||||||
expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()"
|
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()"
|
||||||
---
|
---
|
||||||
"ServerInfo"
|
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: burrow/src/daemon/command.rs
|
source: burrow/src/daemon/command.rs
|
||||||
expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()"
|
expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()"
|
||||||
---
|
---
|
||||||
"Stop"
|
"ServerInfo"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: burrow/src/daemon/command.rs
|
source: burrow/src/daemon/command.rs
|
||||||
expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()"
|
expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()"
|
||||||
---
|
---
|
||||||
"ServerConfig"
|
"Stop"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
source: burrow/src/daemon/command.rs
|
||||||
|
expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()"
|
||||||
|
---
|
||||||
|
"ServerConfig"
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
source: burrow/src/daemon/command.rs
|
source: burrow/src/daemon/command.rs
|
||||||
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
|
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
|
||||||
---
|
---
|
||||||
{"Start":{"tun":{"name":null,"no_pi":null,"tun_excl":null}}}
|
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
source: burrow/src/daemon/response.rs
|
source: burrow/src/daemon/response.rs
|
||||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
|
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
|
||||||
---
|
---
|
||||||
{"result":{"Ok":{"ServerConfig":{"address":"10.0.0.1","name":null,"mtu":null}}},"id":0}
|
{"result":{"Ok":{"ServerConfig":{"address":"10.13.13.2","name":null,"mtu":null}}},"id":0}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
// Check capabilities on Linux
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
#[instrument]
|
|
||||||
pub fn ensure_root() {
|
|
||||||
use caps::{has_cap, CapSet, Capability};
|
|
||||||
|
|
||||||
let cap_net_admin = Capability::CAP_NET_ADMIN;
|
|
||||||
if let Ok(has_cap) = has_cap(None, CapSet::Effective, cap_net_admin) {
|
|
||||||
if !has_cap {
|
|
||||||
eprintln!(
|
|
||||||
"This action needs the CAP_NET_ADMIN permission. Did you mean to run it as root?"
|
|
||||||
);
|
|
||||||
std::process::exit(77);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Failed to check capabilities. Please file a bug report!");
|
|
||||||
std::process::exit(71);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for root user on macOS
|
|
||||||
#[cfg(target_vendor = "apple")]
|
|
||||||
#[instrument]
|
|
||||||
pub fn ensure_root() {
|
|
||||||
use nix::unistd::Uid;
|
|
||||||
|
|
||||||
let current_uid = Uid::current();
|
|
||||||
if !current_uid.is_root() {
|
|
||||||
eprintln!("This action must be run as root!");
|
|
||||||
std::process::exit(77);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "windows")]
|
|
||||||
#[instrument]
|
|
||||||
pub fn ensure_root() {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
#![deny(missing_debug_implementations)]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
pub mod ensureroot;
|
pub mod wireguard;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
use std::{
|
|
||||||
mem,
|
|
||||||
os::fd::{AsRawFd, FromRawFd},
|
|
||||||
};
|
|
||||||
|
|
||||||
use tun::TunInterface;
|
|
||||||
|
|
||||||
// TODO Separate start and retrieve functions
|
|
||||||
|
|
||||||
mod daemon;
|
mod daemon;
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
pub use daemon::{
|
pub use daemon::{
|
||||||
DaemonClient, DaemonCommand, DaemonResponse, DaemonResponseData, DaemonStartOptions, ServerInfo,
|
DaemonClient,
|
||||||
|
DaemonCommand,
|
||||||
|
DaemonResponse,
|
||||||
|
DaemonResponseData,
|
||||||
|
DaemonStartOptions,
|
||||||
|
ServerInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
|
|
@ -23,24 +18,3 @@ mod apple;
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
pub use apple::*;
|
pub use apple::*;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn retrieve() -> i32 {
|
|
||||||
let iface2 = (1..100)
|
|
||||||
.filter_map(|i| {
|
|
||||||
let iface = unsafe { TunInterface::from_raw_fd(i) };
|
|
||||||
match iface.name() {
|
|
||||||
Ok(_name) => Some(iface),
|
|
||||||
Err(_) => {
|
|
||||||
mem::forget(iface);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.next();
|
|
||||||
match iface2 {
|
|
||||||
Some(iface) => iface.as_raw_fd(),
|
|
||||||
None => -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
use anyhow::Context;
|
use anyhow::{Context, Result};
|
||||||
use std::mem;
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
|
||||||
use std::os::fd::FromRawFd;
|
|
||||||
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use tracing::{instrument, Level};
|
use tracing::instrument;
|
||||||
|
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use tracing_oslog::OsLogger;
|
use tracing_oslog::OsLogger;
|
||||||
use tracing_subscriber::{prelude::*, FmtSubscriber, EnvFilter};
|
use tracing_subscriber::{prelude::*, EnvFilter, FmtSubscriber};
|
||||||
use anyhow::Result;
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
use burrow::retrieve;
|
|
||||||
use tun::TunInterface;
|
use tun::TunInterface;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
mod daemon;
|
mod daemon;
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
mod wireguard;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions};
|
use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions};
|
||||||
|
use tun::TunOptions;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
use crate::daemon::DaemonResponseData;
|
use crate::daemon::DaemonResponseData;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|
@ -64,17 +64,32 @@ struct DaemonArgs {}
|
||||||
async fn try_start() -> Result<()> {
|
async fn try_start() -> Result<()> {
|
||||||
let mut client = DaemonClient::new().await?;
|
let mut client = DaemonClient::new().await?;
|
||||||
client
|
client
|
||||||
.send_command(DaemonCommand::Start(DaemonStartOptions::default()))
|
.send_command(DaemonCommand::Start(DaemonStartOptions {
|
||||||
|
tun: TunOptions::new().address("10.13.13.2"),
|
||||||
|
}))
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(target_vendor = "apple")]
|
||||||
#[instrument]
|
#[instrument]
|
||||||
async fn try_retrieve() -> Result<()> {
|
async fn try_retrieve() -> Result<()> {
|
||||||
burrow::ensureroot::ensure_root();
|
LogTracer::init()
|
||||||
let iface2 = retrieve();
|
.context("Failed to initialize LogTracer")
|
||||||
tracing::info!("{}", iface2);
|
.unwrap();
|
||||||
|
|
||||||
|
if cfg!(target_os = "linux") || cfg!(target_vendor = "apple") {
|
||||||
|
let maybe_layer = system_log().unwrap();
|
||||||
|
if let Some(layer) = maybe_layer {
|
||||||
|
let logger = layer.with_subscriber(FmtSubscriber::new());
|
||||||
|
tracing::subscriber::set_global_default(logger)
|
||||||
|
.context("Failed to set the global tracing subscriber")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let iface2 = TunInterface::retrieve().ok_or(anyhow::anyhow!("No interface found"))?;
|
||||||
|
tracing::info!("{:?}", iface2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,9 +104,10 @@ async fn initialize_tracing() -> Result<()> {
|
||||||
FmtSubscriber::builder()
|
FmtSubscriber::builder()
|
||||||
.with_line_number(true)
|
.with_line_number(true)
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
.finish()
|
.finish(),
|
||||||
);
|
);
|
||||||
tracing::subscriber::set_global_default(logger).context("Failed to set the global tracing subscriber")?;
|
tracing::subscriber::set_global_default(logger)
|
||||||
|
.context("Failed to set the global tracing subscriber")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +122,7 @@ async fn try_stop() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
async fn try_serverinfo() -> Result<()>{
|
async fn try_serverinfo() -> Result<()> {
|
||||||
let mut client = DaemonClient::new().await?;
|
let mut client = DaemonClient::new().await?;
|
||||||
let res = client.send_command(DaemonCommand::ServerInfo).await?;
|
let res = client.send_command(DaemonCommand::ServerInfo).await?;
|
||||||
match res.result {
|
match res.result {
|
||||||
|
|
@ -116,7 +132,9 @@ async fn try_serverinfo() -> Result<()>{
|
||||||
Ok(DaemonResponseData::None) => {
|
Ok(DaemonResponseData::None) => {
|
||||||
println!("Server not started.")
|
println!("Server not started.")
|
||||||
}
|
}
|
||||||
Ok(res) => {println!("Unexpected Response: {:?}", res)}
|
Ok(res) => {
|
||||||
|
println!("Unexpected Response: {:?}", res)
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error when retrieving from server: {}", e)
|
println!("Error when retrieving from server: {}", e)
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +143,7 @@ async fn try_serverinfo() -> Result<()>{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
async fn try_serverconfig() -> Result<()>{
|
async fn try_serverconfig() -> Result<()> {
|
||||||
let mut client = DaemonClient::new().await?;
|
let mut client = DaemonClient::new().await?;
|
||||||
let res = client.send_command(DaemonCommand::ServerConfig).await?;
|
let res = client.send_command(DaemonCommand::ServerConfig).await?;
|
||||||
match res.result {
|
match res.result {
|
||||||
|
|
@ -135,7 +153,9 @@ async fn try_serverconfig() -> Result<()>{
|
||||||
Ok(DaemonResponseData::None) => {
|
Ok(DaemonResponseData::None) => {
|
||||||
println!("Server not started.")
|
println!("Server not started.")
|
||||||
}
|
}
|
||||||
Ok(res) => {println!("Unexpected Response: {:?}", res)}
|
Ok(res) => {
|
||||||
|
println!("Unexpected Response: {:?}", res)
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error when retrieving from server: {}", e)
|
println!("Error when retrieving from server: {}", e)
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +168,7 @@ async fn try_start() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
#[cfg(not(target_vendor = "apple"))]
|
||||||
async fn try_retrieve() -> Result<()> {
|
async fn try_retrieve() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +187,7 @@ async fn try_serverinfo() -> Result<()> {
|
||||||
async fn try_serverconfig() -> Result<()> {
|
async fn try_serverconfig() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
initialize_tracing().await?;
|
initialize_tracing().await?;
|
||||||
|
|
@ -186,30 +207,31 @@ async fn main() -> Result<()> {
|
||||||
try_stop().await?;
|
try_stop().await?;
|
||||||
}
|
}
|
||||||
Commands::Daemon(_) => daemon::daemon_main().await?,
|
Commands::Daemon(_) => daemon::daemon_main().await?,
|
||||||
Commands::ServerInfo => {
|
Commands::ServerInfo => try_serverinfo().await?,
|
||||||
try_serverinfo().await?
|
Commands::ServerConfig => try_serverconfig().await?,
|
||||||
}
|
|
||||||
Commands::ServerConfig => {
|
|
||||||
try_serverconfig().await?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn system_log() -> anyhow::Result<Option<tracing_journald::Layer>> {
|
fn system_log() -> Result<Option<tracing_journald::Layer>> {
|
||||||
let maybe_journald = tracing_journald::layer();
|
let maybe_journald = tracing_journald::layer();
|
||||||
match maybe_journald {
|
match maybe_journald {
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
tracing::trace!("journald not found");
|
tracing::trace!("journald not found");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
},
|
}
|
||||||
_ => Ok(Some(maybe_journald?))
|
_ => Ok(Some(maybe_journald?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
fn system_log() -> anyhow::Result<Option<OsLogger>> {
|
fn system_log() -> Result<Option<OsLogger>> {
|
||||||
Ok(Some(OsLogger::new("com.hackclub.burrow", "burrow-cli")))
|
Ok(Some(OsLogger::new("com.hackclub.burrow", "burrow-cli")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
||||||
|
pub fn main() {
|
||||||
|
eprintln!("This platform is not supported currently.")
|
||||||
|
}
|
||||||
|
|
|
||||||
112
burrow/src/wireguard/config.rs
Normal file
112
burrow/src/wireguard/config.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
use std::{net::ToSocketAddrs, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Error, Result};
|
||||||
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
use fehler::throws;
|
||||||
|
use ip_network::IpNetwork;
|
||||||
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
use crate::wireguard::{Interface as WgInterface, Peer as WgPeer};
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn parse_key(string: &str) -> [u8; 32] {
|
||||||
|
let value = general_purpose::STANDARD.decode(string)?;
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
key.copy_from_slice(&value[..]);
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn parse_secret_key(string: &str) -> StaticSecret {
|
||||||
|
let key = parse_key(string)?;
|
||||||
|
StaticSecret::from(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn parse_public_key(string: &str) -> PublicKey {
|
||||||
|
let key = parse_key(string)?;
|
||||||
|
PublicKey::from(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A raw version of Peer Config that can be used later to reflect configuration files.
|
||||||
|
/// This should be later converted to a `WgPeer`.
|
||||||
|
/// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview
|
||||||
|
pub struct Peer {
|
||||||
|
pub public_key: String,
|
||||||
|
pub preshared_key: Option<String>,
|
||||||
|
pub allowed_ips: Vec<String>,
|
||||||
|
pub endpoint: String,
|
||||||
|
pub persistent_keepalive: Option<u32>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Interface {
|
||||||
|
pub private_key: String,
|
||||||
|
pub address: String,
|
||||||
|
pub listen_port: u32,
|
||||||
|
pub dns: Vec<String>,
|
||||||
|
pub mtu: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub peers: Vec<Peer>,
|
||||||
|
pub interface: Interface, // Support for multiple interfaces?
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Config> for WgInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(cfig: Config) -> Result<Self, Error> {
|
||||||
|
let sk = parse_secret_key(&cfig.interface.private_key)?;
|
||||||
|
let wg_peers: Vec<WgPeer> = cfig
|
||||||
|
.peers
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
Ok(WgPeer {
|
||||||
|
private_key: sk.clone(),
|
||||||
|
public_key: parse_public_key(&p.public_key)?,
|
||||||
|
endpoint: p
|
||||||
|
.endpoint
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.find(|sock| sock.is_ipv4())
|
||||||
|
.ok_or(anyhow!("DNS Lookup Fails!"))?,
|
||||||
|
preshared_key: match &p.preshared_key {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(k) => parse_key(k).map(Some),
|
||||||
|
}?,
|
||||||
|
allowed_ips: p
|
||||||
|
.allowed_ips
|
||||||
|
.iter()
|
||||||
|
.map(|ip_addr| {
|
||||||
|
IpNetwork::from_str(ip_addr)
|
||||||
|
.map_err(|e| anyhow!("Error parsing IP Network {}: {}", ip_addr, e))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<IpNetwork>>>()?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<WgPeer>>>()?;
|
||||||
|
WgInterface::new(wg_peers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
interface: Interface {
|
||||||
|
private_key: "GNqIAOCRxjl/cicZyvkvpTklgQuUmGUIEkH7IXF/sEE=".into(),
|
||||||
|
address: "10.13.13.2/24".into(),
|
||||||
|
listen_port: 51820,
|
||||||
|
dns: Default::default(),
|
||||||
|
mtu: Default::default(),
|
||||||
|
},
|
||||||
|
peers: vec![Peer {
|
||||||
|
endpoint: "wg.burrow.rs:51820".into(),
|
||||||
|
allowed_ips: vec!["8.8.8.8/32".into()],
|
||||||
|
public_key: "uy75leriJay0+oHLhRMpV+A5xAQ0hCJ+q7Ww81AOvT4=".into(),
|
||||||
|
preshared_key: Some("s7lx/mg+reVEMnGnqeyYOQkzD86n2+gYnx1M9ygi08k=".into()),
|
||||||
|
persistent_keepalive: Default::default(),
|
||||||
|
name: Default::default(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
burrow/src/wireguard/iface.rs
Executable file
160
burrow/src/wireguard/iface.rs
Executable file
|
|
@ -0,0 +1,160 @@
|
||||||
|
use std::{net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use fehler::throws;
|
||||||
|
use futures::future::join_all;
|
||||||
|
use ip_network_table::IpNetworkTable;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tracing::{debug, error};
|
||||||
|
use tun::tokio::TunInterface;
|
||||||
|
|
||||||
|
use super::{noise::Tunnel, Peer, PeerPcb};
|
||||||
|
|
||||||
|
struct IndexedPcbs {
|
||||||
|
pcbs: Vec<Arc<PeerPcb>>,
|
||||||
|
allowed_ips: IpNetworkTable<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexedPcbs {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pcbs: vec![],
|
||||||
|
allowed_ips: IpNetworkTable::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, pcb: PeerPcb) {
|
||||||
|
let idx: usize = self.pcbs.len();
|
||||||
|
for allowed_ip in pcb.allowed_ips.iter() {
|
||||||
|
self.allowed_ips.insert(*allowed_ip, idx);
|
||||||
|
}
|
||||||
|
self.pcbs.insert(idx, Arc::new(pcb));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(&self, addr: IpAddr) -> Option<usize> {
|
||||||
|
let (_, &idx) = self.allowed_ips.longest_match(addr)?;
|
||||||
|
Some(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<PeerPcb> for IndexedPcbs {
|
||||||
|
fn from_iter<I: IntoIterator<Item = PeerPcb>>(iter: I) -> Self {
|
||||||
|
iter.into_iter().fold(Self::new(), |mut acc, pcb| {
|
||||||
|
acc.insert(pcb);
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Interface {
|
||||||
|
tun: Option<Arc<RwLock<TunInterface>>>,
|
||||||
|
pcbs: Arc<IndexedPcbs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interface {
|
||||||
|
#[throws]
|
||||||
|
pub fn new<I: IntoIterator<Item = Peer>>(peers: I) -> Self {
|
||||||
|
let pcbs: IndexedPcbs = peers
|
||||||
|
.into_iter()
|
||||||
|
.map(PeerPcb::new)
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let pcbs = Arc::new(pcbs);
|
||||||
|
Self { pcbs, tun: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tun(&mut self, tun: Arc<RwLock<TunInterface>>) {
|
||||||
|
self.tun = Some(tun);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self) -> anyhow::Result<()> {
|
||||||
|
let pcbs = self.pcbs.clone();
|
||||||
|
let tun = self
|
||||||
|
.tun
|
||||||
|
.clone()
|
||||||
|
.ok_or(anyhow::anyhow!("tun interface does not exist"))?;
|
||||||
|
log::info!("Starting interface");
|
||||||
|
|
||||||
|
let outgoing = async move {
|
||||||
|
loop {
|
||||||
|
let mut buf = [0u8; 3000];
|
||||||
|
|
||||||
|
let src = {
|
||||||
|
let src = match tun.read().await.recv(&mut buf[..]).await {
|
||||||
|
Ok(len) => &buf[..len],
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to read from interface: {}", e);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!("Read {} bytes from interface", src.len());
|
||||||
|
src
|
||||||
|
};
|
||||||
|
|
||||||
|
let dst_addr = match Tunnel::dst_address(src) {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => {
|
||||||
|
debug!("No destination found");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Routing packet to {}", dst_addr);
|
||||||
|
|
||||||
|
let Some(idx) = pcbs.find(dst_addr) else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Found peer:{}", idx);
|
||||||
|
|
||||||
|
match pcbs.pcbs[idx].send(src).await {
|
||||||
|
Ok(..) => {
|
||||||
|
let addr = pcbs.pcbs[idx].endpoint;
|
||||||
|
debug!("Sent packet to peer {}", addr);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to send packet {}", e);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tsks = vec![];
|
||||||
|
let tun = self
|
||||||
|
.tun
|
||||||
|
.clone()
|
||||||
|
.ok_or(anyhow::anyhow!("tun interface does not exist"))?;
|
||||||
|
let outgoing = tokio::task::spawn(outgoing);
|
||||||
|
tsks.push(outgoing);
|
||||||
|
debug!("preparing to spawn read tasks");
|
||||||
|
|
||||||
|
{
|
||||||
|
let pcbs = &self.pcbs;
|
||||||
|
for i in 0..pcbs.pcbs.len() {
|
||||||
|
debug!("spawning read task for peer {}", i);
|
||||||
|
let pcb = pcbs.pcbs[i].clone();
|
||||||
|
let tun = tun.clone();
|
||||||
|
let tsk = async move {
|
||||||
|
if let Err(e) = pcb.open_if_closed().await {
|
||||||
|
log::error!("failed to open pcb: {}", e);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let r2 = pcb.run(tun).await;
|
||||||
|
if let Err(e) = r2 {
|
||||||
|
log::error!("failed to run pcb: {}", e);
|
||||||
|
} else {
|
||||||
|
debug!("pcb ran successfully");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!("task made..");
|
||||||
|
tsks.push(tokio::spawn(tsk));
|
||||||
|
}
|
||||||
|
debug!("spawned read tasks");
|
||||||
|
}
|
||||||
|
debug!("preparing to join..");
|
||||||
|
join_all(tsks).await;
|
||||||
|
debug!("joined!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
11
burrow/src/wireguard/mod.rs
Executable file
11
burrow/src/wireguard/mod.rs
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
mod config;
|
||||||
|
mod iface;
|
||||||
|
mod noise;
|
||||||
|
mod pcb;
|
||||||
|
mod peer;
|
||||||
|
|
||||||
|
pub use config::Config;
|
||||||
|
pub use iface::Interface;
|
||||||
|
pub use pcb::PeerPcb;
|
||||||
|
pub use peer::Peer;
|
||||||
|
pub use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
20
burrow/src/wireguard/noise/errors.rs
Executable file
20
burrow/src/wireguard/noise/errors.rs
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WireGuardError {
|
||||||
|
DestinationBufferTooSmall,
|
||||||
|
UnexpectedPacket,
|
||||||
|
WrongIndex,
|
||||||
|
WrongKey,
|
||||||
|
InvalidTai64nTimestamp,
|
||||||
|
WrongTai64nTimestamp,
|
||||||
|
InvalidMac,
|
||||||
|
InvalidAeadTag,
|
||||||
|
InvalidCounter,
|
||||||
|
DuplicateCounter,
|
||||||
|
InvalidPacket,
|
||||||
|
NoCurrentSession,
|
||||||
|
ConnectionExpired,
|
||||||
|
UnderLoad,
|
||||||
|
}
|
||||||
900
burrow/src/wireguard/noise/handshake.rs
Executable file
900
burrow/src/wireguard/noise/handshake.rs
Executable file
|
|
@ -0,0 +1,900 @@
|
||||||
|
// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
convert::TryInto,
|
||||||
|
time::{Duration, Instant, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aead::{Aead, Payload};
|
||||||
|
use blake2::{
|
||||||
|
digest::{FixedOutput, KeyInit},
|
||||||
|
Blake2s256,
|
||||||
|
Blake2sMac,
|
||||||
|
Digest,
|
||||||
|
};
|
||||||
|
use chacha20poly1305::XChaCha20Poly1305;
|
||||||
|
use rand_core::OsRng;
|
||||||
|
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
errors::WireGuardError,
|
||||||
|
session::Session,
|
||||||
|
x25519,
|
||||||
|
HandshakeInit,
|
||||||
|
HandshakeResponse,
|
||||||
|
PacketCookieReply,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) const LABEL_MAC1: &[u8; 8] = b"mac1----";
|
||||||
|
pub(crate) const LABEL_COOKIE: &[u8; 8] = b"cookie--";
|
||||||
|
const KEY_LEN: usize = 32;
|
||||||
|
const TIMESTAMP_LEN: usize = 12;
|
||||||
|
|
||||||
|
// initiator.chaining_key = HASH(CONSTRUCTION)
|
||||||
|
const INITIAL_CHAIN_KEY: [u8; KEY_LEN] = [
|
||||||
|
96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248,
|
||||||
|
114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54,
|
||||||
|
];
|
||||||
|
|
||||||
|
// initiator.chaining_hash = HASH(initiator.chaining_key || IDENTIFIER)
|
||||||
|
const INITIAL_CHAIN_HASH: [u8; KEY_LEN] = [
|
||||||
|
34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34,
|
||||||
|
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn b2s_hash(data1: &[u8], data2: &[u8]) -> [u8; 32] {
|
||||||
|
let mut hash = Blake2s256::new();
|
||||||
|
hash.update(data1);
|
||||||
|
hash.update(data2);
|
||||||
|
hash.finalize().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// RFC 2401 HMAC+Blake2s, not to be confused with *keyed* Blake2s
|
||||||
|
pub(crate) fn b2s_hmac(key: &[u8], data1: &[u8]) -> [u8; 32] {
|
||||||
|
use blake2::digest::Update;
|
||||||
|
type HmacBlake2s = hmac::SimpleHmac<Blake2s256>;
|
||||||
|
let mut hmac = HmacBlake2s::new_from_slice(key).unwrap();
|
||||||
|
hmac.update(data1);
|
||||||
|
hmac.finalize_fixed().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Like b2s_hmac, but chain data1 and data2 together
|
||||||
|
pub(crate) fn b2s_hmac2(key: &[u8], data1: &[u8], data2: &[u8]) -> [u8; 32] {
|
||||||
|
use blake2::digest::Update;
|
||||||
|
type HmacBlake2s = hmac::SimpleHmac<Blake2s256>;
|
||||||
|
let mut hmac = HmacBlake2s::new_from_slice(key).unwrap();
|
||||||
|
hmac.update(data1);
|
||||||
|
hmac.update(data2);
|
||||||
|
hmac.finalize_fixed().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn b2s_keyed_mac_16(key: &[u8], data1: &[u8]) -> [u8; 16] {
|
||||||
|
let mut hmac = Blake2sMac::new_from_slice(key).unwrap();
|
||||||
|
blake2::digest::Update::update(&mut hmac, data1);
|
||||||
|
hmac.finalize_fixed().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn b2s_keyed_mac_16_2(key: &[u8], data1: &[u8], data2: &[u8]) -> [u8; 16] {
|
||||||
|
let mut hmac = Blake2sMac::new_from_slice(key).unwrap();
|
||||||
|
blake2::digest::Update::update(&mut hmac, data1);
|
||||||
|
blake2::digest::Update::update(&mut hmac, data2);
|
||||||
|
hmac.finalize_fixed().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn b2s_mac_24(key: &[u8], data1: &[u8]) -> [u8; 24] {
|
||||||
|
let mut hmac = Blake2sMac::new_from_slice(key).unwrap();
|
||||||
|
blake2::digest::Update::update(&mut hmac, data1);
|
||||||
|
hmac.finalize_fixed().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// This wrapper involves an extra copy and MAY BE SLOWER
|
||||||
|
fn aead_chacha20_seal(ciphertext: &mut [u8], key: &[u8], counter: u64, data: &[u8], aad: &[u8]) {
|
||||||
|
let mut nonce: [u8; 12] = [0; 12];
|
||||||
|
nonce[4..12].copy_from_slice(&counter.to_le_bytes());
|
||||||
|
|
||||||
|
aead_chacha20_seal_inner(ciphertext, key, nonce, data, aad)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn aead_chacha20_seal_inner(
|
||||||
|
ciphertext: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: [u8; 12],
|
||||||
|
data: &[u8],
|
||||||
|
aad: &[u8],
|
||||||
|
) {
|
||||||
|
let key = LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, key).unwrap());
|
||||||
|
|
||||||
|
ciphertext[..data.len()].copy_from_slice(data);
|
||||||
|
|
||||||
|
let tag = key
|
||||||
|
.seal_in_place_separate_tag(
|
||||||
|
Nonce::assume_unique_for_key(nonce),
|
||||||
|
Aad::from(aad),
|
||||||
|
&mut ciphertext[..data.len()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ciphertext[data.len()..].copy_from_slice(tag.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// This wrapper involves an extra copy and MAY BE SLOWER
|
||||||
|
fn aead_chacha20_open(
|
||||||
|
buffer: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
counter: u64,
|
||||||
|
data: &[u8],
|
||||||
|
aad: &[u8],
|
||||||
|
) -> Result<(), WireGuardError> {
|
||||||
|
let mut nonce: [u8; 12] = [0; 12];
|
||||||
|
nonce[4..].copy_from_slice(&counter.to_le_bytes());
|
||||||
|
aead_chacha20_open_inner(buffer, key, nonce, data, aad)
|
||||||
|
.map_err(|_| WireGuardError::InvalidAeadTag)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn aead_chacha20_open_inner(
|
||||||
|
buffer: &mut [u8],
|
||||||
|
key: &[u8],
|
||||||
|
nonce: [u8; 12],
|
||||||
|
data: &[u8],
|
||||||
|
aad: &[u8],
|
||||||
|
) -> Result<(), ring::error::Unspecified> {
|
||||||
|
let key = LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, key).unwrap());
|
||||||
|
|
||||||
|
let mut inner_buffer = data.to_owned();
|
||||||
|
|
||||||
|
let plaintext = key.open_in_place(
|
||||||
|
Nonce::assume_unique_for_key(nonce),
|
||||||
|
Aad::from(aad),
|
||||||
|
&mut inner_buffer,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
buffer.copy_from_slice(plaintext);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// This struct represents a 12 byte [Tai64N](https://cr.yp.to/libtai/tai64.html) timestamp
|
||||||
|
struct Tai64N {
|
||||||
|
secs: u64,
|
||||||
|
nano: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// This struct computes a [Tai64N](https://cr.yp.to/libtai/tai64.html) timestamp from current system time
|
||||||
|
struct TimeStamper {
|
||||||
|
duration_at_start: Duration,
|
||||||
|
instant_at_start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeStamper {
|
||||||
|
/// Create a new TimeStamper
|
||||||
|
pub fn new() -> TimeStamper {
|
||||||
|
TimeStamper {
|
||||||
|
duration_at_start: SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap(),
|
||||||
|
instant_at_start: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take time reading and generate a 12 byte timestamp
|
||||||
|
pub fn stamp(&self) -> [u8; 12] {
|
||||||
|
const TAI64_BASE: u64 = (1u64 << 62) + 37;
|
||||||
|
let mut ext_stamp = [0u8; 12];
|
||||||
|
let stamp = Instant::now().duration_since(self.instant_at_start) + self.duration_at_start;
|
||||||
|
ext_stamp[0..8].copy_from_slice(&(stamp.as_secs() + TAI64_BASE).to_be_bytes());
|
||||||
|
ext_stamp[8..12].copy_from_slice(&stamp.subsec_nanos().to_be_bytes());
|
||||||
|
ext_stamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tai64N {
|
||||||
|
/// A zeroed out timestamp
|
||||||
|
fn zero() -> Tai64N {
|
||||||
|
Tai64N { secs: 0, nano: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a timestamp from a 12 byte u8 slice
|
||||||
|
fn parse(buf: &[u8; 12]) -> Result<Tai64N, WireGuardError> {
|
||||||
|
if buf.len() < 12 {
|
||||||
|
return Err(WireGuardError::InvalidTai64nTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sec_bytes, nano_bytes) = buf.split_at(std::mem::size_of::<u64>());
|
||||||
|
let secs = u64::from_be_bytes(sec_bytes.try_into().unwrap());
|
||||||
|
let nano = u32::from_be_bytes(nano_bytes.try_into().unwrap());
|
||||||
|
|
||||||
|
// WireGuard does not actually expect tai64n timestamp, just monotonically
|
||||||
|
// increasing one if secs < (1u64 << 62) || secs >= (1u64 << 63) {
|
||||||
|
// return Err(WireGuardError::InvalidTai64nTimestamp);
|
||||||
|
//};
|
||||||
|
// if nano >= 1_000_000_000 {
|
||||||
|
// return Err(WireGuardError::InvalidTai64nTimestamp);
|
||||||
|
//}
|
||||||
|
|
||||||
|
Ok(Tai64N { secs, nano })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this timestamp represents a time that is chronologically after
|
||||||
|
/// the time represented by the other timestamp
|
||||||
|
pub fn after(&self, other: &Tai64N) -> bool {
|
||||||
|
(self.secs > other.secs) || ((self.secs == other.secs) && (self.nano > other.nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters used by the noise protocol
|
||||||
|
struct NoiseParams {
|
||||||
|
/// Our static public key
|
||||||
|
static_public: x25519::PublicKey,
|
||||||
|
/// Our static private key
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
/// Static public key of the other party
|
||||||
|
peer_static_public: x25519::PublicKey,
|
||||||
|
/// A shared key = DH(static_private, peer_static_public)
|
||||||
|
static_shared: x25519::SharedSecret,
|
||||||
|
/// A pre-computation of HASH("mac1----", peer_static_public) for this peer
|
||||||
|
sending_mac1_key: [u8; KEY_LEN],
|
||||||
|
/// An optional preshared key
|
||||||
|
preshared_key: Option<[u8; KEY_LEN]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for NoiseParams {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("NoiseParams")
|
||||||
|
.field("static_public", &self.static_public)
|
||||||
|
.field("static_private", &"<redacted>")
|
||||||
|
.field("peer_static_public", &self.peer_static_public)
|
||||||
|
.field("static_shared", &"<redacted>")
|
||||||
|
.field("sending_mac1_key", &self.sending_mac1_key)
|
||||||
|
.field("preshared_key", &self.preshared_key)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HandshakeInitSentState {
|
||||||
|
local_index: u32,
|
||||||
|
hash: [u8; KEY_LEN],
|
||||||
|
chaining_key: [u8; KEY_LEN],
|
||||||
|
ephemeral_private: x25519::ReusableSecret,
|
||||||
|
time_sent: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for HandshakeInitSentState {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("HandshakeInitSentState")
|
||||||
|
.field("local_index", &self.local_index)
|
||||||
|
.field("hash", &self.hash)
|
||||||
|
.field("chaining_key", &self.chaining_key)
|
||||||
|
.field("ephemeral_private", &"<redacted>")
|
||||||
|
.field("time_sent", &self.time_sent)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum HandshakeState {
|
||||||
|
/// No handshake in process
|
||||||
|
None,
|
||||||
|
/// We initiated the handshake
|
||||||
|
InitSent(HandshakeInitSentState),
|
||||||
|
/// Handshake initiated by peer
|
||||||
|
InitReceived {
|
||||||
|
hash: [u8; KEY_LEN],
|
||||||
|
chaining_key: [u8; KEY_LEN],
|
||||||
|
peer_ephemeral_public: x25519::PublicKey,
|
||||||
|
peer_index: u32,
|
||||||
|
},
|
||||||
|
/// Handshake was established too long ago (implies no handshake is in
|
||||||
|
/// progress)
|
||||||
|
Expired,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handshake {
|
||||||
|
params: NoiseParams,
|
||||||
|
/// Index of the next session
|
||||||
|
next_index: u32,
|
||||||
|
/// Allow to have two outgoing handshakes in flight, because sometimes we
|
||||||
|
/// may receive a delayed response to a handshake with bad networks
|
||||||
|
previous: HandshakeState,
|
||||||
|
/// Current handshake state
|
||||||
|
state: HandshakeState,
|
||||||
|
cookies: Cookies,
|
||||||
|
/// The timestamp of the last handshake we received
|
||||||
|
last_handshake_timestamp: Tai64N,
|
||||||
|
// TODO: make TimeStamper a singleton
|
||||||
|
stamper: TimeStamper,
|
||||||
|
pub(super) last_rtt: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct Cookies {
|
||||||
|
last_mac1: Option<[u8; 16]>,
|
||||||
|
index: u32,
|
||||||
|
write_cookie: Option<[u8; 16]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HalfHandshake {
|
||||||
|
pub peer_index: u32,
|
||||||
|
pub peer_static_public: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_handshake_anon(
|
||||||
|
static_private: &x25519::StaticSecret,
|
||||||
|
static_public: &x25519::PublicKey,
|
||||||
|
packet: &HandshakeInit,
|
||||||
|
) -> Result<HalfHandshake, WireGuardError> {
|
||||||
|
let peer_index = packet.sender_idx;
|
||||||
|
// initiator.chaining_key = HASH(CONSTRUCTION)
|
||||||
|
let mut chaining_key = INITIAL_CHAIN_KEY;
|
||||||
|
// initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) ||
|
||||||
|
// responder.static_public)
|
||||||
|
let mut hash = INITIAL_CHAIN_HASH;
|
||||||
|
hash = b2s_hash(&hash, static_public.as_bytes());
|
||||||
|
// msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
|
||||||
|
let peer_ephemeral_public = x25519::PublicKey::from(*packet.unencrypted_ephemeral);
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral)
|
||||||
|
hash = b2s_hash(&hash, peer_ephemeral_public.as_bytes());
|
||||||
|
// temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral)
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(
|
||||||
|
&b2s_hmac(&chaining_key, peer_ephemeral_public.as_bytes()),
|
||||||
|
&[0x01],
|
||||||
|
);
|
||||||
|
// temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private,
|
||||||
|
// responder.static_public))
|
||||||
|
let ephemeral_shared = static_private.diffie_hellman(&peer_ephemeral_public);
|
||||||
|
let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes());
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// key = HMAC(temp, initiator.chaining_key || 0x2)
|
||||||
|
let key = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
|
||||||
|
let mut peer_static_public = [0u8; KEY_LEN];
|
||||||
|
// msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash)
|
||||||
|
aead_chacha20_open(
|
||||||
|
&mut peer_static_public,
|
||||||
|
&key,
|
||||||
|
0,
|
||||||
|
packet.encrypted_static,
|
||||||
|
&hash,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(HalfHandshake { peer_index, peer_static_public })
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoiseParams {
|
||||||
|
/// New noise params struct from our secret key, peers public key, and
|
||||||
|
/// optional preshared key
|
||||||
|
fn new(
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
static_public: x25519::PublicKey,
|
||||||
|
peer_static_public: x25519::PublicKey,
|
||||||
|
preshared_key: Option<[u8; 32]>,
|
||||||
|
) -> Result<NoiseParams, WireGuardError> {
|
||||||
|
let static_shared = static_private.diffie_hellman(&peer_static_public);
|
||||||
|
|
||||||
|
let initial_sending_mac_key = b2s_hash(LABEL_MAC1, peer_static_public.as_bytes());
|
||||||
|
|
||||||
|
Ok(NoiseParams {
|
||||||
|
static_public,
|
||||||
|
static_private,
|
||||||
|
peer_static_public,
|
||||||
|
static_shared,
|
||||||
|
sending_mac1_key: initial_sending_mac_key,
|
||||||
|
preshared_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new private key
|
||||||
|
fn set_static_private(
|
||||||
|
&mut self,
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
static_public: x25519::PublicKey,
|
||||||
|
) -> Result<(), WireGuardError> {
|
||||||
|
// Check that the public key indeed matches the private key
|
||||||
|
let check_key = x25519::PublicKey::from(&static_private);
|
||||||
|
assert_eq!(check_key.as_bytes(), static_public.as_bytes());
|
||||||
|
|
||||||
|
self.static_private = static_private;
|
||||||
|
self.static_public = static_public;
|
||||||
|
|
||||||
|
self.static_shared = self.static_private.diffie_hellman(&self.peer_static_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handshake {
|
||||||
|
pub(crate) fn new(
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
static_public: x25519::PublicKey,
|
||||||
|
peer_static_public: x25519::PublicKey,
|
||||||
|
global_idx: u32,
|
||||||
|
preshared_key: Option<[u8; 32]>,
|
||||||
|
) -> Result<Handshake, WireGuardError> {
|
||||||
|
let params = NoiseParams::new(
|
||||||
|
static_private,
|
||||||
|
static_public,
|
||||||
|
peer_static_public,
|
||||||
|
preshared_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Handshake {
|
||||||
|
params,
|
||||||
|
next_index: global_idx,
|
||||||
|
previous: HandshakeState::None,
|
||||||
|
state: HandshakeState::None,
|
||||||
|
last_handshake_timestamp: Tai64N::zero(),
|
||||||
|
stamper: TimeStamper::new(),
|
||||||
|
cookies: Default::default(),
|
||||||
|
last_rtt: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_in_progress(&self) -> bool {
|
||||||
|
!matches!(self.state, HandshakeState::None | HandshakeState::Expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn timer(&self) -> Option<Instant> {
|
||||||
|
match self.state {
|
||||||
|
HandshakeState::InitSent(HandshakeInitSentState { time_sent, .. }) => Some(time_sent),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_expired(&mut self) {
|
||||||
|
self.previous = HandshakeState::Expired;
|
||||||
|
self.state = HandshakeState::Expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_expired(&self) -> bool {
|
||||||
|
matches!(self.state, HandshakeState::Expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has_cookie(&self) -> bool {
|
||||||
|
self.cookies.write_cookie.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_cookie(&mut self) {
|
||||||
|
self.cookies.write_cookie = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The index used is 24 bits for peer index, allowing for 16M active peers per
|
||||||
|
// server and 8 bits for cyclic session index
|
||||||
|
fn inc_index(&mut self) -> u32 {
|
||||||
|
let index = self.next_index;
|
||||||
|
let idx8 = index as u8;
|
||||||
|
self.next_index = (index & !0xff) | u32::from(idx8.wrapping_add(1));
|
||||||
|
self.next_index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_static_private(
|
||||||
|
&mut self,
|
||||||
|
private_key: x25519::StaticSecret,
|
||||||
|
public_key: x25519::PublicKey,
|
||||||
|
) -> Result<(), WireGuardError> {
|
||||||
|
self.params.set_static_private(private_key, public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn receive_handshake_initialization<'a>(
|
||||||
|
&mut self,
|
||||||
|
packet: HandshakeInit,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<(&'a mut [u8], Session), WireGuardError> {
|
||||||
|
// initiator.chaining_key = HASH(CONSTRUCTION)
|
||||||
|
let mut chaining_key = INITIAL_CHAIN_KEY;
|
||||||
|
// initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) ||
|
||||||
|
// responder.static_public)
|
||||||
|
let mut hash = INITIAL_CHAIN_HASH;
|
||||||
|
hash = b2s_hash(&hash, self.params.static_public.as_bytes());
|
||||||
|
// msg.sender_index = little_endian(initiator.sender_index)
|
||||||
|
let peer_index = packet.sender_idx;
|
||||||
|
// msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
|
||||||
|
let peer_ephemeral_public = x25519::PublicKey::from(*packet.unencrypted_ephemeral);
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral)
|
||||||
|
hash = b2s_hash(&hash, peer_ephemeral_public.as_bytes());
|
||||||
|
// temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral)
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(
|
||||||
|
&b2s_hmac(&chaining_key, peer_ephemeral_public.as_bytes()),
|
||||||
|
&[0x01],
|
||||||
|
);
|
||||||
|
// temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private,
|
||||||
|
// responder.static_public))
|
||||||
|
let ephemeral_shared = self
|
||||||
|
.params
|
||||||
|
.static_private
|
||||||
|
.diffie_hellman(&peer_ephemeral_public);
|
||||||
|
let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes());
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// key = HMAC(temp, initiator.chaining_key || 0x2)
|
||||||
|
let key = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
|
||||||
|
let mut peer_static_public_decrypted = [0u8; KEY_LEN];
|
||||||
|
// msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash)
|
||||||
|
aead_chacha20_open(
|
||||||
|
&mut peer_static_public_decrypted,
|
||||||
|
&key,
|
||||||
|
0,
|
||||||
|
packet.encrypted_static,
|
||||||
|
&hash,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ring::constant_time::verify_slices_are_equal(
|
||||||
|
self.params.peer_static_public.as_bytes(),
|
||||||
|
&peer_static_public_decrypted,
|
||||||
|
)
|
||||||
|
.map_err(|_| WireGuardError::WrongKey)?;
|
||||||
|
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.encrypted_static)
|
||||||
|
hash = b2s_hash(&hash, packet.encrypted_static);
|
||||||
|
// temp = HMAC(initiator.chaining_key, DH(initiator.static_private,
|
||||||
|
// responder.static_public))
|
||||||
|
let temp = b2s_hmac(&chaining_key, self.params.static_shared.as_bytes());
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// key = HMAC(temp, initiator.chaining_key || 0x2)
|
||||||
|
let key = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
// msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash)
|
||||||
|
let mut timestamp = [0u8; TIMESTAMP_LEN];
|
||||||
|
aead_chacha20_open(&mut timestamp, &key, 0, packet.encrypted_timestamp, &hash)?;
|
||||||
|
|
||||||
|
let timestamp = Tai64N::parse(×tamp)?;
|
||||||
|
if !timestamp.after(&self.last_handshake_timestamp) {
|
||||||
|
// Possibly a replay
|
||||||
|
return Err(WireGuardError::WrongTai64nTimestamp)
|
||||||
|
}
|
||||||
|
self.last_handshake_timestamp = timestamp;
|
||||||
|
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp)
|
||||||
|
hash = b2s_hash(&hash, packet.encrypted_timestamp);
|
||||||
|
|
||||||
|
self.previous = std::mem::replace(&mut self.state, HandshakeState::InitReceived {
|
||||||
|
chaining_key,
|
||||||
|
hash,
|
||||||
|
peer_ephemeral_public,
|
||||||
|
peer_index,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.format_handshake_response(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn receive_handshake_response(
|
||||||
|
&mut self,
|
||||||
|
packet: HandshakeResponse,
|
||||||
|
) -> Result<Session, WireGuardError> {
|
||||||
|
// Check if there is a handshake awaiting a response and return the correct one
|
||||||
|
let (state, is_previous) = match (&self.state, &self.previous) {
|
||||||
|
(HandshakeState::InitSent(s), _) if s.local_index == packet.receiver_idx => (s, false),
|
||||||
|
(_, HandshakeState::InitSent(s)) if s.local_index == packet.receiver_idx => (s, true),
|
||||||
|
_ => return Err(WireGuardError::UnexpectedPacket),
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer_index = packet.sender_idx;
|
||||||
|
let local_index = state.local_index;
|
||||||
|
|
||||||
|
let unencrypted_ephemeral = x25519::PublicKey::from(*packet.unencrypted_ephemeral);
|
||||||
|
// msg.unencrypted_ephemeral = DH_PUBKEY(responder.ephemeral_private)
|
||||||
|
// responder.hash = HASH(responder.hash || msg.unencrypted_ephemeral)
|
||||||
|
let mut hash = b2s_hash(&state.hash, unencrypted_ephemeral.as_bytes());
|
||||||
|
// temp = HMAC(responder.chaining_key, msg.unencrypted_ephemeral)
|
||||||
|
let temp = b2s_hmac(&state.chaining_key, unencrypted_ephemeral.as_bytes());
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
let mut chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private,
|
||||||
|
// initiator.ephemeral_public))
|
||||||
|
let ephemeral_shared = state
|
||||||
|
.ephemeral_private
|
||||||
|
.diffie_hellman(&unencrypted_ephemeral);
|
||||||
|
let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes());
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private,
|
||||||
|
// initiator.static_public))
|
||||||
|
let temp = b2s_hmac(
|
||||||
|
&chaining_key,
|
||||||
|
&self
|
||||||
|
.params
|
||||||
|
.static_private
|
||||||
|
.diffie_hellman(&unencrypted_ephemeral)
|
||||||
|
.to_bytes(),
|
||||||
|
);
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, preshared_key)
|
||||||
|
let temp = b2s_hmac(
|
||||||
|
&chaining_key,
|
||||||
|
&self.params.preshared_key.unwrap_or([0u8; 32])[..],
|
||||||
|
);
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp2 = HMAC(temp, responder.chaining_key || 0x2)
|
||||||
|
let temp2 = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
// key = HMAC(temp, temp2 || 0x3)
|
||||||
|
let key = b2s_hmac2(&temp, &temp2, &[0x03]);
|
||||||
|
// responder.hash = HASH(responder.hash || temp2)
|
||||||
|
hash = b2s_hash(&hash, &temp2);
|
||||||
|
// msg.encrypted_nothing = AEAD(key, 0, [empty], responder.hash)
|
||||||
|
aead_chacha20_open(&mut [], &key, 0, packet.encrypted_nothing, &hash)?;
|
||||||
|
|
||||||
|
// responder.hash = HASH(responder.hash || msg.encrypted_nothing)
|
||||||
|
// hash = b2s_hash(hash, buf[ENC_NOTHING_OFF..ENC_NOTHING_OFF +
|
||||||
|
// ENC_NOTHING_SZ]);
|
||||||
|
|
||||||
|
// Derive keys
|
||||||
|
// temp1 = HMAC(initiator.chaining_key, [empty])
|
||||||
|
// temp2 = HMAC(temp1, 0x1)
|
||||||
|
// temp3 = HMAC(temp1, temp2 || 0x2)
|
||||||
|
// initiator.sending_key = temp2
|
||||||
|
// initiator.receiving_key = temp3
|
||||||
|
// initiator.sending_key_counter = 0
|
||||||
|
// initiator.receiving_key_counter = 0
|
||||||
|
let temp1 = b2s_hmac(&chaining_key, &[]);
|
||||||
|
let temp2 = b2s_hmac(&temp1, &[0x01]);
|
||||||
|
let temp3 = b2s_hmac2(&temp1, &temp2, &[0x02]);
|
||||||
|
|
||||||
|
let rtt_time = Instant::now().duration_since(state.time_sent);
|
||||||
|
self.last_rtt = Some(rtt_time.as_millis() as u32);
|
||||||
|
|
||||||
|
if is_previous {
|
||||||
|
self.previous = HandshakeState::None;
|
||||||
|
} else {
|
||||||
|
self.state = HandshakeState::None;
|
||||||
|
}
|
||||||
|
Ok(Session::new(local_index, peer_index, temp3, temp2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn receive_cookie_reply(
|
||||||
|
&mut self,
|
||||||
|
packet: PacketCookieReply,
|
||||||
|
) -> Result<(), WireGuardError> {
|
||||||
|
let mac1 = match self.cookies.last_mac1 {
|
||||||
|
Some(mac) => mac,
|
||||||
|
None => return Err(WireGuardError::UnexpectedPacket),
|
||||||
|
};
|
||||||
|
|
||||||
|
let local_index = self.cookies.index;
|
||||||
|
if packet.receiver_idx != local_index {
|
||||||
|
return Err(WireGuardError::WrongIndex)
|
||||||
|
}
|
||||||
|
// msg.encrypted_cookie = XAEAD(HASH(LABEL_COOKIE || responder.static_public),
|
||||||
|
// msg.nonce, cookie, last_received_msg.mac1)
|
||||||
|
let key = b2s_hash(LABEL_COOKIE, self.params.peer_static_public.as_bytes()); // TODO: pre-compute
|
||||||
|
|
||||||
|
let payload = Payload {
|
||||||
|
aad: &mac1[0..16],
|
||||||
|
msg: packet.encrypted_cookie,
|
||||||
|
};
|
||||||
|
let plaintext = XChaCha20Poly1305::new_from_slice(&key)
|
||||||
|
.unwrap()
|
||||||
|
.decrypt(packet.nonce.into(), payload)
|
||||||
|
.map_err(|_| WireGuardError::InvalidAeadTag)?;
|
||||||
|
|
||||||
|
let cookie = plaintext
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| WireGuardError::InvalidPacket)?;
|
||||||
|
self.cookies.write_cookie = Some(cookie);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute and append mac1 and mac2 to a handshake message
|
||||||
|
fn append_mac1_and_mac2<'a>(
|
||||||
|
&mut self,
|
||||||
|
local_index: u32,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<&'a mut [u8], WireGuardError> {
|
||||||
|
let mac1_off = dst.len() - 32;
|
||||||
|
let mac2_off = dst.len() - 16;
|
||||||
|
|
||||||
|
// msg.mac1 = MAC(HASH(LABEL_MAC1 || responder.static_public),
|
||||||
|
// msg[0:offsetof(msg.mac1)])
|
||||||
|
let msg_mac1 = b2s_keyed_mac_16(&self.params.sending_mac1_key, &dst[..mac1_off]);
|
||||||
|
|
||||||
|
dst[mac1_off..mac2_off].copy_from_slice(&msg_mac1[..]);
|
||||||
|
|
||||||
|
// msg.mac2 = MAC(initiator.last_received_cookie, msg[0:offsetof(msg.mac2)])
|
||||||
|
let msg_mac2: [u8; 16] = if let Some(cookie) = self.cookies.write_cookie {
|
||||||
|
b2s_keyed_mac_16(&cookie, &dst[..mac2_off])
|
||||||
|
} else {
|
||||||
|
[0u8; 16]
|
||||||
|
};
|
||||||
|
|
||||||
|
dst[mac2_off..].copy_from_slice(&msg_mac2[..]);
|
||||||
|
|
||||||
|
self.cookies.index = local_index;
|
||||||
|
self.cookies.last_mac1 = Some(msg_mac1);
|
||||||
|
Ok(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn format_handshake_initiation<'a>(
|
||||||
|
&mut self,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<&'a mut [u8], WireGuardError> {
|
||||||
|
if dst.len() < super::HANDSHAKE_INIT_SZ {
|
||||||
|
return Err(WireGuardError::DestinationBufferTooSmall)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (message_type, rest) = dst.split_at_mut(4);
|
||||||
|
let (sender_index, rest) = rest.split_at_mut(4);
|
||||||
|
let (unencrypted_ephemeral, rest) = rest.split_at_mut(32);
|
||||||
|
let (encrypted_static, rest) = rest.split_at_mut(32 + 16);
|
||||||
|
let (encrypted_timestamp, _) = rest.split_at_mut(12 + 16);
|
||||||
|
|
||||||
|
let local_index = self.inc_index();
|
||||||
|
|
||||||
|
// initiator.chaining_key = HASH(CONSTRUCTION)
|
||||||
|
let mut chaining_key = INITIAL_CHAIN_KEY;
|
||||||
|
// initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) ||
|
||||||
|
// responder.static_public)
|
||||||
|
let mut hash = INITIAL_CHAIN_HASH;
|
||||||
|
hash = b2s_hash(&hash, self.params.peer_static_public.as_bytes());
|
||||||
|
// initiator.ephemeral_private = DH_GENERATE()
|
||||||
|
let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng);
|
||||||
|
// msg.message_type = 1
|
||||||
|
// msg.reserved_zero = { 0, 0, 0 }
|
||||||
|
message_type.copy_from_slice(&super::HANDSHAKE_INIT.to_le_bytes());
|
||||||
|
// msg.sender_index = little_endian(initiator.sender_index)
|
||||||
|
sender_index.copy_from_slice(&local_index.to_le_bytes());
|
||||||
|
// msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
|
||||||
|
unencrypted_ephemeral
|
||||||
|
.copy_from_slice(x25519::PublicKey::from(&ephemeral_private).as_bytes());
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral)
|
||||||
|
hash = b2s_hash(&hash, unencrypted_ephemeral);
|
||||||
|
// temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral)
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&b2s_hmac(&chaining_key, unencrypted_ephemeral), &[0x01]);
|
||||||
|
// temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private,
|
||||||
|
// responder.static_public))
|
||||||
|
let ephemeral_shared = ephemeral_private.diffie_hellman(&self.params.peer_static_public);
|
||||||
|
let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes());
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// key = HMAC(temp, initiator.chaining_key || 0x2)
|
||||||
|
let key = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
// msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash)
|
||||||
|
aead_chacha20_seal(
|
||||||
|
encrypted_static,
|
||||||
|
&key,
|
||||||
|
0,
|
||||||
|
self.params.static_public.as_bytes(),
|
||||||
|
&hash,
|
||||||
|
);
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.encrypted_static)
|
||||||
|
hash = b2s_hash(&hash, encrypted_static);
|
||||||
|
// temp = HMAC(initiator.chaining_key, DH(initiator.static_private,
|
||||||
|
// responder.static_public))
|
||||||
|
let temp = b2s_hmac(&chaining_key, self.params.static_shared.as_bytes());
|
||||||
|
// initiator.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// key = HMAC(temp, initiator.chaining_key || 0x2)
|
||||||
|
let key = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
// msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash)
|
||||||
|
let timestamp = self.stamper.stamp();
|
||||||
|
aead_chacha20_seal(encrypted_timestamp, &key, 0, ×tamp, &hash);
|
||||||
|
// initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp)
|
||||||
|
hash = b2s_hash(&hash, encrypted_timestamp);
|
||||||
|
|
||||||
|
let time_now = Instant::now();
|
||||||
|
self.previous = std::mem::replace(
|
||||||
|
&mut self.state,
|
||||||
|
HandshakeState::InitSent(HandshakeInitSentState {
|
||||||
|
local_index,
|
||||||
|
chaining_key,
|
||||||
|
hash,
|
||||||
|
ephemeral_private,
|
||||||
|
time_sent: time_now,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.append_mac1_and_mac2(local_index, &mut dst[..super::HANDSHAKE_INIT_SZ])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_handshake_response<'a>(
|
||||||
|
&mut self,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<(&'a mut [u8], Session), WireGuardError> {
|
||||||
|
if dst.len() < super::HANDSHAKE_RESP_SZ {
|
||||||
|
return Err(WireGuardError::DestinationBufferTooSmall)
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = std::mem::replace(&mut self.state, HandshakeState::None);
|
||||||
|
let (mut chaining_key, mut hash, peer_ephemeral_public, peer_index) = match state {
|
||||||
|
HandshakeState::InitReceived {
|
||||||
|
chaining_key,
|
||||||
|
hash,
|
||||||
|
peer_ephemeral_public,
|
||||||
|
peer_index,
|
||||||
|
} => (chaining_key, hash, peer_ephemeral_public, peer_index),
|
||||||
|
_ => {
|
||||||
|
panic!("Unexpected attempt to call send_handshake_response");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (message_type, rest) = dst.split_at_mut(4);
|
||||||
|
let (sender_index, rest) = rest.split_at_mut(4);
|
||||||
|
let (receiver_index, rest) = rest.split_at_mut(4);
|
||||||
|
let (unencrypted_ephemeral, rest) = rest.split_at_mut(32);
|
||||||
|
let (encrypted_nothing, _) = rest.split_at_mut(16);
|
||||||
|
|
||||||
|
// responder.ephemeral_private = DH_GENERATE()
|
||||||
|
let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng);
|
||||||
|
let local_index = self.inc_index();
|
||||||
|
// msg.message_type = 2
|
||||||
|
// msg.reserved_zero = { 0, 0, 0 }
|
||||||
|
message_type.copy_from_slice(&super::HANDSHAKE_RESP.to_le_bytes());
|
||||||
|
// msg.sender_index = little_endian(responder.sender_index)
|
||||||
|
sender_index.copy_from_slice(&local_index.to_le_bytes());
|
||||||
|
// msg.receiver_index = little_endian(initiator.sender_index)
|
||||||
|
receiver_index.copy_from_slice(&peer_index.to_le_bytes());
|
||||||
|
// msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
|
||||||
|
unencrypted_ephemeral
|
||||||
|
.copy_from_slice(x25519::PublicKey::from(&ephemeral_private).as_bytes());
|
||||||
|
// responder.hash = HASH(responder.hash || msg.unencrypted_ephemeral)
|
||||||
|
hash = b2s_hash(&hash, unencrypted_ephemeral);
|
||||||
|
// temp = HMAC(responder.chaining_key, msg.unencrypted_ephemeral)
|
||||||
|
let temp = b2s_hmac(&chaining_key, unencrypted_ephemeral);
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private,
|
||||||
|
// initiator.ephemeral_public))
|
||||||
|
let ephemeral_shared = ephemeral_private.diffie_hellman(&peer_ephemeral_public);
|
||||||
|
let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes());
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private,
|
||||||
|
// initiator.static_public))
|
||||||
|
let temp = b2s_hmac(
|
||||||
|
&chaining_key,
|
||||||
|
&ephemeral_private
|
||||||
|
.diffie_hellman(&self.params.peer_static_public)
|
||||||
|
.to_bytes(),
|
||||||
|
);
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp = HMAC(responder.chaining_key, preshared_key)
|
||||||
|
let temp = b2s_hmac(
|
||||||
|
&chaining_key,
|
||||||
|
&self.params.preshared_key.unwrap_or([0u8; 32])[..],
|
||||||
|
);
|
||||||
|
// responder.chaining_key = HMAC(temp, 0x1)
|
||||||
|
chaining_key = b2s_hmac(&temp, &[0x01]);
|
||||||
|
// temp2 = HMAC(temp, responder.chaining_key || 0x2)
|
||||||
|
let temp2 = b2s_hmac2(&temp, &chaining_key, &[0x02]);
|
||||||
|
// key = HMAC(temp, temp2 || 0x3)
|
||||||
|
let key = b2s_hmac2(&temp, &temp2, &[0x03]);
|
||||||
|
// responder.hash = HASH(responder.hash || temp2)
|
||||||
|
hash = b2s_hash(&hash, &temp2);
|
||||||
|
// msg.encrypted_nothing = AEAD(key, 0, [empty], responder.hash)
|
||||||
|
aead_chacha20_seal(encrypted_nothing, &key, 0, &[], &hash);
|
||||||
|
|
||||||
|
// Derive keys
|
||||||
|
// temp1 = HMAC(initiator.chaining_key, [empty])
|
||||||
|
// temp2 = HMAC(temp1, 0x1)
|
||||||
|
// temp3 = HMAC(temp1, temp2 || 0x2)
|
||||||
|
// initiator.sending_key = temp2
|
||||||
|
// initiator.receiving_key = temp3
|
||||||
|
// initiator.sending_key_counter = 0
|
||||||
|
// initiator.receiving_key_counter = 0
|
||||||
|
let temp1 = b2s_hmac(&chaining_key, &[]);
|
||||||
|
let temp2 = b2s_hmac(&temp1, &[0x01]);
|
||||||
|
let temp3 = b2s_hmac2(&temp1, &temp2, &[0x02]);
|
||||||
|
|
||||||
|
let dst = self.append_mac1_and_mac2(local_index, &mut dst[..super::HANDSHAKE_RESP_SZ])?;
|
||||||
|
|
||||||
|
Ok((dst, Session::new(local_index, peer_index, temp2, temp3)))
|
||||||
|
}
|
||||||
|
}
|
||||||
634
burrow/src/wireguard/noise/mod.rs
Executable file
634
burrow/src/wireguard/noise/mod.rs
Executable file
|
|
@ -0,0 +1,634 @@
|
||||||
|
// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
pub mod handshake;
|
||||||
|
pub mod rate_limiter;
|
||||||
|
|
||||||
|
mod session;
|
||||||
|
mod timers;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use errors::WireGuardError;
|
||||||
|
use handshake::Handshake;
|
||||||
|
use rate_limiter::RateLimiter;
|
||||||
|
use timers::{TimerName, Timers};
|
||||||
|
|
||||||
|
/// The default value to use for rate limiting, when no other rate limiter is
|
||||||
|
/// defined
|
||||||
|
const PEER_HANDSHAKE_RATE_LIMIT: u64 = 10;
|
||||||
|
|
||||||
|
const IPV4_MIN_HEADER_SIZE: usize = 20;
|
||||||
|
const IPV4_LEN_OFF: usize = 2;
|
||||||
|
const IPV4_SRC_IP_OFF: usize = 12;
|
||||||
|
const IPV4_DST_IP_OFF: usize = 16;
|
||||||
|
const IPV4_IP_SZ: usize = 4;
|
||||||
|
|
||||||
|
const IPV6_MIN_HEADER_SIZE: usize = 40;
|
||||||
|
const IPV6_LEN_OFF: usize = 4;
|
||||||
|
const IPV6_SRC_IP_OFF: usize = 8;
|
||||||
|
const IPV6_DST_IP_OFF: usize = 24;
|
||||||
|
const IPV6_IP_SZ: usize = 16;
|
||||||
|
|
||||||
|
const IP_LEN_SZ: usize = 2;
|
||||||
|
|
||||||
|
const MAX_QUEUE_DEPTH: usize = 256;
|
||||||
|
/// number of sessions in the ring, better keep a PoT
|
||||||
|
const N_SESSIONS: usize = 8;
|
||||||
|
|
||||||
|
pub mod x25519 {
|
||||||
|
pub use x25519_dalek::{
|
||||||
|
EphemeralSecret,
|
||||||
|
PublicKey,
|
||||||
|
ReusableSecret,
|
||||||
|
SharedSecret,
|
||||||
|
StaticSecret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TunnResult<'a> {
|
||||||
|
Done,
|
||||||
|
Err(WireGuardError),
|
||||||
|
WriteToNetwork(&'a mut [u8]),
|
||||||
|
WriteToTunnelV4(&'a mut [u8], Ipv4Addr),
|
||||||
|
WriteToTunnelV6(&'a mut [u8], Ipv6Addr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<WireGuardError> for TunnResult<'a> {
|
||||||
|
fn from(err: WireGuardError) -> TunnResult<'a> {
|
||||||
|
TunnResult::Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tunnel represents a point-to-point WireGuard connection
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Tunnel {
|
||||||
|
/// The handshake currently in progress
|
||||||
|
handshake: handshake::Handshake,
|
||||||
|
/// The N_SESSIONS most recent sessions, index is session id modulo
|
||||||
|
/// N_SESSIONS
|
||||||
|
sessions: [Option<session::Session>; N_SESSIONS],
|
||||||
|
/// Index of most recently used session
|
||||||
|
current: usize,
|
||||||
|
/// Queue to store blocked packets
|
||||||
|
packet_queue: VecDeque<Vec<u8>>,
|
||||||
|
/// Keeps tabs on the expiring timers
|
||||||
|
timers: timers::Timers,
|
||||||
|
tx_bytes: usize,
|
||||||
|
rx_bytes: usize,
|
||||||
|
rate_limiter: Arc<RateLimiter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageType = u32;
|
||||||
|
const HANDSHAKE_INIT: MessageType = 1;
|
||||||
|
const HANDSHAKE_RESP: MessageType = 2;
|
||||||
|
const COOKIE_REPLY: MessageType = 3;
|
||||||
|
const DATA: MessageType = 4;
|
||||||
|
|
||||||
|
const HANDSHAKE_INIT_SZ: usize = 148;
|
||||||
|
const HANDSHAKE_RESP_SZ: usize = 92;
|
||||||
|
const COOKIE_REPLY_SZ: usize = 64;
|
||||||
|
const DATA_OVERHEAD_SZ: usize = 32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HandshakeInit<'a> {
|
||||||
|
sender_idx: u32,
|
||||||
|
unencrypted_ephemeral: &'a [u8; 32],
|
||||||
|
encrypted_static: &'a [u8],
|
||||||
|
encrypted_timestamp: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HandshakeResponse<'a> {
|
||||||
|
sender_idx: u32,
|
||||||
|
pub receiver_idx: u32,
|
||||||
|
unencrypted_ephemeral: &'a [u8; 32],
|
||||||
|
encrypted_nothing: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PacketCookieReply<'a> {
|
||||||
|
pub receiver_idx: u32,
|
||||||
|
nonce: &'a [u8],
|
||||||
|
encrypted_cookie: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PacketData<'a> {
|
||||||
|
pub receiver_idx: u32,
|
||||||
|
counter: u64,
|
||||||
|
encrypted_encapsulated_packet: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a packet from network
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Packet<'a> {
|
||||||
|
HandshakeInit(HandshakeInit<'a>),
|
||||||
|
HandshakeResponse(HandshakeResponse<'a>),
|
||||||
|
CookieReply(PacketCookieReply<'a>),
|
||||||
|
Data(PacketData<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tunnel {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn parse_incoming_packet(src: &[u8]) -> Result<Packet, WireGuardError> {
|
||||||
|
if src.len() < 4 {
|
||||||
|
return Err(WireGuardError::InvalidPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the type, as well as the reserved zero fields
|
||||||
|
let packet_type = u32::from_le_bytes(src[0..4].try_into().unwrap());
|
||||||
|
tracing::debug!("packet_type: {}", packet_type);
|
||||||
|
|
||||||
|
Ok(match (packet_type, src.len()) {
|
||||||
|
(HANDSHAKE_INIT, HANDSHAKE_INIT_SZ) => Packet::HandshakeInit(HandshakeInit {
|
||||||
|
sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
|
||||||
|
unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[8..40])
|
||||||
|
.expect("length already checked above"),
|
||||||
|
encrypted_static: &src[40..88],
|
||||||
|
encrypted_timestamp: &src[88..116],
|
||||||
|
}),
|
||||||
|
(HANDSHAKE_RESP, HANDSHAKE_RESP_SZ) => Packet::HandshakeResponse(HandshakeResponse {
|
||||||
|
sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
|
||||||
|
receiver_idx: u32::from_le_bytes(src[8..12].try_into().unwrap()),
|
||||||
|
unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[12..44])
|
||||||
|
.expect("length already checked above"),
|
||||||
|
encrypted_nothing: &src[44..60],
|
||||||
|
}),
|
||||||
|
(COOKIE_REPLY, COOKIE_REPLY_SZ) => Packet::CookieReply(PacketCookieReply {
|
||||||
|
receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
|
||||||
|
nonce: &src[8..32],
|
||||||
|
encrypted_cookie: &src[32..64],
|
||||||
|
}),
|
||||||
|
(DATA, DATA_OVERHEAD_SZ..=std::usize::MAX) => Packet::Data(PacketData {
|
||||||
|
receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
|
||||||
|
counter: u64::from_le_bytes(src[8..16].try_into().unwrap()),
|
||||||
|
encrypted_encapsulated_packet: &src[16..],
|
||||||
|
}),
|
||||||
|
_ => return Err(WireGuardError::InvalidPacket),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_expired(&self) -> bool {
|
||||||
|
self.handshake.is_expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dst_address(packet: &[u8]) -> Option<IpAddr> {
|
||||||
|
if packet.is_empty() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
match packet[0] >> 4 {
|
||||||
|
4 if packet.len() >= IPV4_MIN_HEADER_SIZE => {
|
||||||
|
let addr_bytes: [u8; IPV4_IP_SZ] = packet
|
||||||
|
[IPV4_DST_IP_OFF..IPV4_DST_IP_OFF + IPV4_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Some(IpAddr::from(addr_bytes))
|
||||||
|
}
|
||||||
|
6 if packet.len() >= IPV6_MIN_HEADER_SIZE => {
|
||||||
|
let addr_bytes: [u8; IPV6_IP_SZ] = packet
|
||||||
|
[IPV6_DST_IP_OFF..IPV6_DST_IP_OFF + IPV6_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Some(IpAddr::from(addr_bytes))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn src_address(packet: &[u8]) -> Option<IpAddr> {
|
||||||
|
if packet.is_empty() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
match packet[0] >> 4 {
|
||||||
|
4 if packet.len() >= IPV4_MIN_HEADER_SIZE => {
|
||||||
|
let addr_bytes: [u8; IPV4_IP_SZ] = packet
|
||||||
|
[IPV4_SRC_IP_OFF..IPV4_SRC_IP_OFF + IPV4_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Some(IpAddr::from(addr_bytes))
|
||||||
|
}
|
||||||
|
6 if packet.len() >= IPV6_MIN_HEADER_SIZE => {
|
||||||
|
let addr_bytes: [u8; IPV6_IP_SZ] = packet
|
||||||
|
[IPV6_SRC_IP_OFF..IPV6_SRC_IP_OFF + IPV6_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Some(IpAddr::from(addr_bytes))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new tunnel using own private key and the peer public key
|
||||||
|
pub fn new(
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
peer_static_public: x25519::PublicKey,
|
||||||
|
preshared_key: Option<[u8; 32]>,
|
||||||
|
persistent_keepalive: Option<u16>,
|
||||||
|
index: u32,
|
||||||
|
rate_limiter: Option<Arc<RateLimiter>>,
|
||||||
|
) -> Result<Self, &'static str> {
|
||||||
|
let static_public = x25519::PublicKey::from(&static_private);
|
||||||
|
|
||||||
|
let tunn = Tunnel {
|
||||||
|
handshake: Handshake::new(
|
||||||
|
static_private,
|
||||||
|
static_public,
|
||||||
|
peer_static_public,
|
||||||
|
index << 8,
|
||||||
|
preshared_key,
|
||||||
|
)
|
||||||
|
.map_err(|_| "Invalid parameters")?,
|
||||||
|
sessions: Default::default(),
|
||||||
|
current: Default::default(),
|
||||||
|
tx_bytes: Default::default(),
|
||||||
|
rx_bytes: Default::default(),
|
||||||
|
|
||||||
|
packet_queue: VecDeque::new(),
|
||||||
|
timers: Timers::new(persistent_keepalive, rate_limiter.is_none()),
|
||||||
|
|
||||||
|
rate_limiter: rate_limiter.unwrap_or_else(|| {
|
||||||
|
Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT))
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tunn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the private key and clear existing sessions
|
||||||
|
pub fn set_static_private(
|
||||||
|
&mut self,
|
||||||
|
static_private: x25519::StaticSecret,
|
||||||
|
static_public: x25519::PublicKey,
|
||||||
|
rate_limiter: Option<Arc<RateLimiter>>,
|
||||||
|
) -> Result<(), WireGuardError> {
|
||||||
|
self.timers.should_reset_rr = rate_limiter.is_none();
|
||||||
|
self.rate_limiter = rate_limiter.unwrap_or_else(|| {
|
||||||
|
Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT))
|
||||||
|
});
|
||||||
|
self.handshake
|
||||||
|
.set_static_private(static_private, static_public)?;
|
||||||
|
for s in &mut self.sessions {
|
||||||
|
*s = None;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encapsulate a single packet from the tunnel interface.
|
||||||
|
/// Returns TunnResult.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if dst buffer is too small.
|
||||||
|
/// Size of dst should be at least src.len() + 32, and no less than 148
|
||||||
|
/// bytes.
|
||||||
|
pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> {
|
||||||
|
let current = self.current;
|
||||||
|
if let Some(ref session) = self.sessions[current % N_SESSIONS] {
|
||||||
|
// Send the packet using an established session
|
||||||
|
let packet = session.format_packet_data(src, dst);
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketSent);
|
||||||
|
// Exclude Keepalive packets from timer update.
|
||||||
|
if !src.is_empty() {
|
||||||
|
self.timer_tick(TimerName::TimeLastDataPacketSent);
|
||||||
|
}
|
||||||
|
self.tx_bytes += src.len();
|
||||||
|
return TunnResult::WriteToNetwork(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no session, queue the packet for future retry
|
||||||
|
self.queue_packet(src);
|
||||||
|
// Initiate a new handshake if none is in progress
|
||||||
|
self.format_handshake_initiation(dst, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a UDP datagram from the network and parses it.
|
||||||
|
/// Returns TunnResult.
|
||||||
|
///
|
||||||
|
/// If the result is of type TunnResult::WriteToNetwork, should repeat the
|
||||||
|
/// call with empty datagram, until TunnResult::Done is returned. If
|
||||||
|
/// batch processing packets, it is OK to defer until last
|
||||||
|
/// packet is processed.
|
||||||
|
pub fn decapsulate<'a>(
|
||||||
|
&mut self,
|
||||||
|
src_addr: Option<IpAddr>,
|
||||||
|
datagram: &[u8],
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> TunnResult<'a> {
|
||||||
|
if datagram.is_empty() {
|
||||||
|
// Indicates a repeated call
|
||||||
|
return self.send_queued_packet(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cookie = [0u8; COOKIE_REPLY_SZ];
|
||||||
|
let packet = match self
|
||||||
|
.rate_limiter
|
||||||
|
.verify_packet(src_addr, datagram, &mut cookie)
|
||||||
|
{
|
||||||
|
Ok(packet) => packet,
|
||||||
|
Err(TunnResult::WriteToNetwork(cookie)) => {
|
||||||
|
dst[..cookie.len()].copy_from_slice(cookie);
|
||||||
|
return TunnResult::WriteToNetwork(&mut dst[..cookie.len()])
|
||||||
|
}
|
||||||
|
Err(TunnResult::Err(e)) => return TunnResult::Err(e),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.handle_verified_packet(packet, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_verified_packet<'a>(
|
||||||
|
&mut self,
|
||||||
|
packet: Packet,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> TunnResult<'a> {
|
||||||
|
match packet {
|
||||||
|
Packet::HandshakeInit(p) => self.handle_handshake_init(p, dst),
|
||||||
|
Packet::HandshakeResponse(p) => self.handle_handshake_response(p, dst),
|
||||||
|
Packet::CookieReply(p) => self.handle_cookie_reply(p),
|
||||||
|
Packet::Data(p) => self.handle_data(p, dst),
|
||||||
|
}
|
||||||
|
.unwrap_or_else(TunnResult::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_handshake_init<'a>(
|
||||||
|
&mut self,
|
||||||
|
p: HandshakeInit,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<TunnResult<'a>, WireGuardError> {
|
||||||
|
tracing::debug!(
|
||||||
|
message = "Received handshake_initiation",
|
||||||
|
remote_idx = p.sender_idx
|
||||||
|
);
|
||||||
|
|
||||||
|
let (packet, session) = self.handshake.receive_handshake_initialization(p, dst)?;
|
||||||
|
|
||||||
|
// Store new session in ring buffer
|
||||||
|
let index = session.local_index();
|
||||||
|
self.sessions[index % N_SESSIONS] = Some(session);
|
||||||
|
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketReceived);
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketSent);
|
||||||
|
self.timer_tick_session_established(false, index); // New session established, we are not the initiator
|
||||||
|
|
||||||
|
tracing::debug!(message = "Sending handshake_response", local_idx = index);
|
||||||
|
|
||||||
|
Ok(TunnResult::WriteToNetwork(packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_handshake_response<'a>(
|
||||||
|
&mut self,
|
||||||
|
p: HandshakeResponse,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<TunnResult<'a>, WireGuardError> {
|
||||||
|
tracing::debug!(
|
||||||
|
message = "Received handshake_response",
|
||||||
|
local_idx = p.receiver_idx,
|
||||||
|
remote_idx = p.sender_idx
|
||||||
|
);
|
||||||
|
|
||||||
|
let session = self.handshake.receive_handshake_response(p)?;
|
||||||
|
|
||||||
|
let keepalive_packet = session.format_packet_data(&[], dst);
|
||||||
|
// Store new session in ring buffer
|
||||||
|
let l_idx = session.local_index();
|
||||||
|
let index = l_idx % N_SESSIONS;
|
||||||
|
self.sessions[index] = Some(session);
|
||||||
|
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketReceived);
|
||||||
|
self.timer_tick_session_established(true, index); // New session established, we are the initiator
|
||||||
|
self.set_current_session(l_idx);
|
||||||
|
|
||||||
|
tracing::debug!("Sending keepalive");
|
||||||
|
|
||||||
|
Ok(TunnResult::WriteToNetwork(keepalive_packet)) // Send a keepalive as
|
||||||
|
// a response
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_cookie_reply<'a>(
|
||||||
|
&mut self,
|
||||||
|
p: PacketCookieReply,
|
||||||
|
) -> Result<TunnResult<'a>, WireGuardError> {
|
||||||
|
tracing::debug!(
|
||||||
|
message = "Received cookie_reply",
|
||||||
|
local_idx = p.receiver_idx
|
||||||
|
);
|
||||||
|
|
||||||
|
self.handshake.receive_cookie_reply(p)?;
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketReceived);
|
||||||
|
self.timer_tick(TimerName::TimeCookieReceived);
|
||||||
|
|
||||||
|
tracing::debug!("Did set cookie");
|
||||||
|
|
||||||
|
Ok(TunnResult::Done)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the index of the currently used session, if needed
|
||||||
|
fn set_current_session(&mut self, new_idx: usize) {
|
||||||
|
let cur_idx = self.current;
|
||||||
|
if cur_idx == new_idx {
|
||||||
|
// There is nothing to do, already using this session, this is the common case
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.sessions[cur_idx % N_SESSIONS].is_none()
|
||||||
|
|| self.timers.session_timers[new_idx % N_SESSIONS]
|
||||||
|
>= self.timers.session_timers[cur_idx % N_SESSIONS]
|
||||||
|
{
|
||||||
|
self.current = new_idx;
|
||||||
|
tracing::debug!(message = "New session", session = new_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a data packet, and stores the decapsulated packet in dst.
|
||||||
|
fn handle_data<'a>(
|
||||||
|
&mut self,
|
||||||
|
packet: PacketData,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<TunnResult<'a>, WireGuardError> {
|
||||||
|
let r_idx = packet.receiver_idx as usize;
|
||||||
|
let idx = r_idx % N_SESSIONS;
|
||||||
|
|
||||||
|
// Get the (probably) right session
|
||||||
|
let decapsulated_packet = {
|
||||||
|
let session = self.sessions[idx].as_ref();
|
||||||
|
let session = session.ok_or_else(|| {
|
||||||
|
tracing::trace!(message = "No current session available", remote_idx = r_idx);
|
||||||
|
WireGuardError::NoCurrentSession
|
||||||
|
})?;
|
||||||
|
session.receive_packet_data(packet, dst)?
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_current_session(r_idx);
|
||||||
|
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketReceived);
|
||||||
|
|
||||||
|
Ok(self.validate_decapsulated_packet(decapsulated_packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a new handshake initiation message and store it in dst. If
|
||||||
|
/// force_resend is true will send a new handshake, even if a handshake
|
||||||
|
/// is already in progress (for example when a handshake times out)
|
||||||
|
pub fn format_handshake_initiation<'a>(
|
||||||
|
&mut self,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
force_resend: bool,
|
||||||
|
) -> TunnResult<'a> {
|
||||||
|
if self.handshake.is_in_progress() && !force_resend {
|
||||||
|
return TunnResult::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.handshake.is_expired() {
|
||||||
|
self.timers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
let starting_new_handshake = !self.handshake.is_in_progress();
|
||||||
|
|
||||||
|
match self.handshake.format_handshake_initiation(dst) {
|
||||||
|
Ok(packet) => {
|
||||||
|
tracing::debug!("Sending handshake_initiation");
|
||||||
|
|
||||||
|
if starting_new_handshake {
|
||||||
|
self.timer_tick(TimerName::TimeLastHandshakeStarted);
|
||||||
|
}
|
||||||
|
self.timer_tick(TimerName::TimeLastPacketSent);
|
||||||
|
TunnResult::WriteToNetwork(packet)
|
||||||
|
}
|
||||||
|
Err(e) => TunnResult::Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if an IP packet is v4 or v6, truncate to the length indicated by
|
||||||
|
/// the length field Returns the truncated packet and the source IP as
|
||||||
|
/// TunnResult
|
||||||
|
fn validate_decapsulated_packet<'a>(&mut self, packet: &'a mut [u8]) -> TunnResult<'a> {
|
||||||
|
let (computed_len, src_ip_address) = match packet.len() {
|
||||||
|
0 => return TunnResult::Done, // This is keepalive, and not an error
|
||||||
|
_ if packet[0] >> 4 == 4 && packet.len() >= IPV4_MIN_HEADER_SIZE => {
|
||||||
|
let len_bytes: [u8; IP_LEN_SZ] = packet[IPV4_LEN_OFF..IPV4_LEN_OFF + IP_LEN_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let addr_bytes: [u8; IPV4_IP_SZ] = packet
|
||||||
|
[IPV4_SRC_IP_OFF..IPV4_SRC_IP_OFF + IPV4_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
(
|
||||||
|
u16::from_be_bytes(len_bytes) as usize,
|
||||||
|
IpAddr::from(addr_bytes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ if packet[0] >> 4 == 6 && packet.len() >= IPV6_MIN_HEADER_SIZE => {
|
||||||
|
let len_bytes: [u8; IP_LEN_SZ] = packet[IPV6_LEN_OFF..IPV6_LEN_OFF + IP_LEN_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let addr_bytes: [u8; IPV6_IP_SZ] = packet
|
||||||
|
[IPV6_SRC_IP_OFF..IPV6_SRC_IP_OFF + IPV6_IP_SZ]
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
(
|
||||||
|
u16::from_be_bytes(len_bytes) as usize + IPV6_MIN_HEADER_SIZE,
|
||||||
|
IpAddr::from(addr_bytes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => return TunnResult::Err(WireGuardError::InvalidPacket),
|
||||||
|
};
|
||||||
|
|
||||||
|
if computed_len > packet.len() {
|
||||||
|
return TunnResult::Err(WireGuardError::InvalidPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.timer_tick(TimerName::TimeLastDataPacketReceived);
|
||||||
|
self.rx_bytes += computed_len;
|
||||||
|
|
||||||
|
match src_ip_address {
|
||||||
|
IpAddr::V4(addr) => TunnResult::WriteToTunnelV4(&mut packet[..computed_len], addr),
|
||||||
|
IpAddr::V6(addr) => TunnResult::WriteToTunnelV6(&mut packet[..computed_len], addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a packet from the queue, and try to encapsulate it
|
||||||
|
fn send_queued_packet<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> {
|
||||||
|
if let Some(packet) = self.dequeue_packet() {
|
||||||
|
match self.encapsulate(&packet, dst) {
|
||||||
|
TunnResult::Err(_) => {
|
||||||
|
// On error, return packet to the queue
|
||||||
|
self.requeue_packet(packet);
|
||||||
|
}
|
||||||
|
r => return r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TunnResult::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push packet to the back of the queue
|
||||||
|
fn queue_packet(&mut self, packet: &[u8]) {
|
||||||
|
if self.packet_queue.len() < MAX_QUEUE_DEPTH {
|
||||||
|
// Drop if too many are already in queue
|
||||||
|
self.packet_queue.push_back(packet.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push packet to the front of the queue
|
||||||
|
fn requeue_packet(&mut self, packet: Vec<u8>) {
|
||||||
|
if self.packet_queue.len() < MAX_QUEUE_DEPTH {
|
||||||
|
// Drop if too many are already in queue
|
||||||
|
self.packet_queue.push_front(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dequeue_packet(&mut self) -> Option<Vec<u8>> {
|
||||||
|
self.packet_queue.pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_loss(&self) -> f32 {
|
||||||
|
let session_idx = self.current;
|
||||||
|
|
||||||
|
let mut weight = 9.0;
|
||||||
|
let mut cur_avg = 0.0;
|
||||||
|
let mut total_weight = 0.0;
|
||||||
|
|
||||||
|
for i in 0..N_SESSIONS {
|
||||||
|
if let Some(ref session) = self.sessions[(session_idx.wrapping_sub(i)) % N_SESSIONS] {
|
||||||
|
let (expected, received) = session.current_packet_cnt();
|
||||||
|
|
||||||
|
let loss = if expected == 0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
1.0 - received as f32 / expected as f32
|
||||||
|
};
|
||||||
|
|
||||||
|
cur_avg += loss * weight;
|
||||||
|
total_weight += weight;
|
||||||
|
weight /= 3.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if total_weight == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
cur_avg / total_weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return stats from the tunnel:
|
||||||
|
/// * Time since last handshake in seconds
|
||||||
|
/// * Data bytes sent
|
||||||
|
/// * Data bytes received
|
||||||
|
pub fn stats(&self) -> (Option<Duration>, usize, usize, f32, Option<u32>) {
|
||||||
|
let time = self.time_since_last_handshake();
|
||||||
|
let tx_bytes = self.tx_bytes;
|
||||||
|
let rx_bytes = self.rx_bytes;
|
||||||
|
let loss = self.estimate_loss();
|
||||||
|
let rtt = self.handshake.last_rtt;
|
||||||
|
|
||||||
|
(time, tx_bytes, rx_bytes, loss, rtt)
|
||||||
|
}
|
||||||
|
}
|
||||||
212
burrow/src/wireguard/noise/rate_limiter.rs
Executable file
212
burrow/src/wireguard/noise/rate_limiter.rs
Executable file
|
|
@ -0,0 +1,212 @@
|
||||||
|
use std::{
|
||||||
|
net::IpAddr,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aead::{generic_array::GenericArray, AeadInPlace, KeyInit};
|
||||||
|
use chacha20poly1305::{Key, XChaCha20Poly1305};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rand_core::{OsRng, RngCore};
|
||||||
|
use ring::constant_time::verify_slices_are_equal;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
handshake::{
|
||||||
|
b2s_hash,
|
||||||
|
b2s_keyed_mac_16,
|
||||||
|
b2s_keyed_mac_16_2,
|
||||||
|
b2s_mac_24,
|
||||||
|
LABEL_COOKIE,
|
||||||
|
LABEL_MAC1,
|
||||||
|
},
|
||||||
|
HandshakeInit,
|
||||||
|
HandshakeResponse,
|
||||||
|
Packet,
|
||||||
|
TunnResult,
|
||||||
|
Tunnel,
|
||||||
|
WireGuardError,
|
||||||
|
};
|
||||||
|
|
||||||
|
const COOKIE_REFRESH: u64 = 128; // Use 128 and not 120 so the compiler can optimize out the division
|
||||||
|
const COOKIE_SIZE: usize = 16;
|
||||||
|
const COOKIE_NONCE_SIZE: usize = 24;
|
||||||
|
|
||||||
|
/// How often should reset count in seconds
|
||||||
|
const RESET_PERIOD: u64 = 1;
|
||||||
|
|
||||||
|
type Cookie = [u8; COOKIE_SIZE];
|
||||||
|
|
||||||
|
/// There are two places where WireGuard requires "randomness" for cookies
|
||||||
|
/// * The 24 byte nonce in the cookie massage - here the only goal is to avoid
|
||||||
|
/// nonce reuse
|
||||||
|
/// * A secret value that changes every two minutes
|
||||||
|
/// Because the main goal of the cookie is simply for a party to prove ownership
|
||||||
|
/// of an IP address we can relax the randomness definition a bit, in order to
|
||||||
|
/// avoid locking, because using less resources is the main goal of any DoS
|
||||||
|
/// prevention mechanism. In order to avoid locking and calls to rand we derive
|
||||||
|
/// pseudo random values using the AEAD and some counters.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RateLimiter {
|
||||||
|
/// The key we use to derive the nonce
|
||||||
|
nonce_key: [u8; 32],
|
||||||
|
/// The key we use to derive the cookie
|
||||||
|
secret_key: [u8; 16],
|
||||||
|
start_time: Instant,
|
||||||
|
/// A single 64 bit counter (should suffice for many years)
|
||||||
|
nonce_ctr: AtomicU64,
|
||||||
|
mac1_key: [u8; 32],
|
||||||
|
cookie_key: Key,
|
||||||
|
limit: u64,
|
||||||
|
/// The counter since last reset
|
||||||
|
count: AtomicU64,
|
||||||
|
/// The time last reset was performed on this rate limiter
|
||||||
|
last_reset: Mutex<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateLimiter {
|
||||||
|
pub fn new(public_key: &super::x25519::PublicKey, limit: u64) -> Self {
|
||||||
|
let mut secret_key = [0u8; 16];
|
||||||
|
OsRng.fill_bytes(&mut secret_key);
|
||||||
|
RateLimiter {
|
||||||
|
nonce_key: Self::rand_bytes(),
|
||||||
|
secret_key,
|
||||||
|
start_time: Instant::now(),
|
||||||
|
nonce_ctr: AtomicU64::new(0),
|
||||||
|
mac1_key: b2s_hash(LABEL_MAC1, public_key.as_bytes()),
|
||||||
|
cookie_key: b2s_hash(LABEL_COOKIE, public_key.as_bytes()).into(),
|
||||||
|
limit,
|
||||||
|
count: AtomicU64::new(0),
|
||||||
|
last_reset: Mutex::new(Instant::now()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_bytes() -> [u8; 32] {
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
OsRng.fill_bytes(&mut key);
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset packet count (ideally should be called with a period of 1 second)
|
||||||
|
pub fn reset_count(&self) {
|
||||||
|
// The rate limiter is not very accurate, but at the scale we care about it
|
||||||
|
// doesn't matter much
|
||||||
|
let current_time = Instant::now();
|
||||||
|
let mut last_reset_time = self.last_reset.lock();
|
||||||
|
if current_time.duration_since(*last_reset_time).as_secs() >= RESET_PERIOD {
|
||||||
|
self.count.store(0, Ordering::SeqCst);
|
||||||
|
*last_reset_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the correct cookie value based on the current secret value and
|
||||||
|
/// the source IP
|
||||||
|
fn current_cookie(&self, addr: IpAddr) -> Cookie {
|
||||||
|
let mut addr_bytes = [0u8; 16];
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
IpAddr::V4(a) => addr_bytes[..4].copy_from_slice(&a.octets()[..]),
|
||||||
|
IpAddr::V6(a) => addr_bytes[..].copy_from_slice(&a.octets()[..]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current cookie for a given IP is the
|
||||||
|
// MAC(responder.changing_secret_every_two_minutes, initiator.ip_address)
|
||||||
|
// First we derive the secret from the current time, the value of cur_counter
|
||||||
|
// would change with time.
|
||||||
|
let cur_counter = Instant::now().duration_since(self.start_time).as_secs() / COOKIE_REFRESH;
|
||||||
|
|
||||||
|
// Next we derive the cookie
|
||||||
|
b2s_keyed_mac_16_2(&self.secret_key, &cur_counter.to_le_bytes(), &addr_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonce(&self) -> [u8; COOKIE_NONCE_SIZE] {
|
||||||
|
let ctr = self.nonce_ctr.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
b2s_mac_24(&self.nonce_key, &ctr.to_le_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_under_load(&self) -> bool {
|
||||||
|
self.count.fetch_add(1, Ordering::SeqCst) >= self.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format_cookie_reply<'a>(
|
||||||
|
&self,
|
||||||
|
idx: u32,
|
||||||
|
cookie: Cookie,
|
||||||
|
mac1: &[u8],
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<&'a mut [u8], WireGuardError> {
|
||||||
|
if dst.len() < super::COOKIE_REPLY_SZ {
|
||||||
|
return Err(WireGuardError::DestinationBufferTooSmall)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (message_type, rest) = dst.split_at_mut(4);
|
||||||
|
let (receiver_index, rest) = rest.split_at_mut(4);
|
||||||
|
let (nonce, rest) = rest.split_at_mut(24);
|
||||||
|
let (encrypted_cookie, _) = rest.split_at_mut(16 + 16);
|
||||||
|
|
||||||
|
// msg.message_type = 3
|
||||||
|
// msg.reserved_zero = { 0, 0, 0 }
|
||||||
|
message_type.copy_from_slice(&super::COOKIE_REPLY.to_le_bytes());
|
||||||
|
// msg.receiver_index = little_endian(initiator.sender_index)
|
||||||
|
receiver_index.copy_from_slice(&idx.to_le_bytes());
|
||||||
|
nonce.copy_from_slice(&self.nonce()[..]);
|
||||||
|
|
||||||
|
let cipher = XChaCha20Poly1305::new(&self.cookie_key);
|
||||||
|
|
||||||
|
let iv = GenericArray::from_slice(nonce);
|
||||||
|
|
||||||
|
encrypted_cookie[..16].copy_from_slice(&cookie);
|
||||||
|
let tag = cipher
|
||||||
|
.encrypt_in_place_detached(iv, mac1, &mut encrypted_cookie[..16])
|
||||||
|
.map_err(|_| WireGuardError::DestinationBufferTooSmall)?;
|
||||||
|
|
||||||
|
encrypted_cookie[16..].copy_from_slice(&tag);
|
||||||
|
|
||||||
|
Ok(&mut dst[..super::COOKIE_REPLY_SZ])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the MAC fields on the datagram, and apply rate limiting if needed
|
||||||
|
pub fn verify_packet<'a, 'b>(
|
||||||
|
&self,
|
||||||
|
src_addr: Option<IpAddr>,
|
||||||
|
src: &'a [u8],
|
||||||
|
dst: &'b mut [u8],
|
||||||
|
) -> Result<Packet<'a>, TunnResult<'b>> {
|
||||||
|
let packet = Tunnel::parse_incoming_packet(src)?;
|
||||||
|
tracing::debug!("packet: {:?}", packet);
|
||||||
|
|
||||||
|
// Verify and rate limit handshake messages only
|
||||||
|
if let Packet::HandshakeInit(HandshakeInit { sender_idx, .. })
|
||||||
|
| Packet::HandshakeResponse(HandshakeResponse { sender_idx, .. }) = packet
|
||||||
|
{
|
||||||
|
tracing::debug!("sender_idx: {}", sender_idx);
|
||||||
|
tracing::debug!("response: {:?}", packet);
|
||||||
|
let (msg, macs) = src.split_at(src.len() - 32);
|
||||||
|
let (mac1, mac2) = macs.split_at(16);
|
||||||
|
|
||||||
|
let computed_mac1 = b2s_keyed_mac_16(&self.mac1_key, msg);
|
||||||
|
verify_slices_are_equal(&computed_mac1[..16], mac1)
|
||||||
|
.map_err(|_| TunnResult::Err(WireGuardError::InvalidMac))?;
|
||||||
|
|
||||||
|
if self.is_under_load() {
|
||||||
|
let addr = match src_addr {
|
||||||
|
None => return Err(TunnResult::Err(WireGuardError::UnderLoad)),
|
||||||
|
Some(addr) => addr,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only given an address can we validate mac2
|
||||||
|
let cookie = self.current_cookie(addr);
|
||||||
|
let computed_mac2 = b2s_keyed_mac_16_2(&cookie, msg, mac1);
|
||||||
|
|
||||||
|
if verify_slices_are_equal(&computed_mac2[..16], mac2).is_err() {
|
||||||
|
let cookie_packet = self
|
||||||
|
.format_cookie_reply(sender_idx, cookie, mac1, dst)
|
||||||
|
.map_err(TunnResult::Err)?;
|
||||||
|
return Err(TunnResult::WriteToNetwork(cookie_packet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
280
burrow/src/wireguard/noise/session.rs
Executable file
280
burrow/src/wireguard/noise/session.rs
Executable file
|
|
@ -0,0 +1,280 @@
|
||||||
|
// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
|
||||||
|
|
||||||
|
use super::{errors::WireGuardError, PacketData};
|
||||||
|
|
||||||
|
pub struct Session {
|
||||||
|
pub(crate) receiving_index: u32,
|
||||||
|
sending_index: u32,
|
||||||
|
receiver: LessSafeKey,
|
||||||
|
sender: LessSafeKey,
|
||||||
|
sending_key_counter: AtomicUsize,
|
||||||
|
receiving_key_counter: Mutex<ReceivingKeyCounterValidator>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Session {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Session: {}<- ->{}",
|
||||||
|
self.receiving_index, self.sending_index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where encrypted data resides in a data packet
|
||||||
|
const DATA_OFFSET: usize = 16;
|
||||||
|
/// The overhead of the AEAD
|
||||||
|
const AEAD_SIZE: usize = 16;
|
||||||
|
|
||||||
|
// Receiving buffer constants
|
||||||
|
const WORD_SIZE: u64 = 64;
|
||||||
|
const N_WORDS: u64 = 16; // Suffice to reorder 64*16 = 1024 packets; can be increased at will
|
||||||
|
const N_BITS: u64 = WORD_SIZE * N_WORDS;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct ReceivingKeyCounterValidator {
|
||||||
|
/// In order to avoid replays while allowing for some reordering of the
|
||||||
|
/// packets, we keep a bitmap of received packets, and the value of the
|
||||||
|
/// highest counter
|
||||||
|
next: u64,
|
||||||
|
/// Used to estimate packet loss
|
||||||
|
receive_cnt: u64,
|
||||||
|
bitmap: [u64; N_WORDS as usize],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceivingKeyCounterValidator {
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_bit(&mut self, idx: u64) {
|
||||||
|
let bit_idx = idx % N_BITS;
|
||||||
|
let word = (bit_idx / WORD_SIZE) as usize;
|
||||||
|
let bit = (bit_idx % WORD_SIZE) as usize;
|
||||||
|
self.bitmap[word] |= 1 << bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn clear_bit(&mut self, idx: u64) {
|
||||||
|
let bit_idx = idx % N_BITS;
|
||||||
|
let word = (bit_idx / WORD_SIZE) as usize;
|
||||||
|
let bit = (bit_idx % WORD_SIZE) as usize;
|
||||||
|
self.bitmap[word] &= !(1u64 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the word that contains idx
|
||||||
|
#[inline(always)]
|
||||||
|
fn clear_word(&mut self, idx: u64) {
|
||||||
|
let bit_idx = idx % N_BITS;
|
||||||
|
let word = (bit_idx / WORD_SIZE) as usize;
|
||||||
|
self.bitmap[word] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if bit is set, false otherwise
|
||||||
|
#[inline(always)]
|
||||||
|
fn check_bit(&self, idx: u64) -> bool {
|
||||||
|
let bit_idx = idx % N_BITS;
|
||||||
|
let word = (bit_idx / WORD_SIZE) as usize;
|
||||||
|
let bit = (bit_idx % WORD_SIZE) as usize;
|
||||||
|
((self.bitmap[word] >> bit) & 1) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the counter was not yet received, and is not too far
|
||||||
|
/// back
|
||||||
|
#[inline(always)]
|
||||||
|
fn will_accept(&self, counter: u64) -> Result<(), WireGuardError> {
|
||||||
|
if counter >= self.next {
|
||||||
|
// As long as the counter is growing no replay took place for sure
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if counter + N_BITS < self.next {
|
||||||
|
// Drop if too far back
|
||||||
|
return Err(WireGuardError::InvalidCounter)
|
||||||
|
}
|
||||||
|
if !self.check_bit(counter) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(WireGuardError::DuplicateCounter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks the counter as received, and returns true if it is still good (in
|
||||||
|
/// case during decryption something changed)
|
||||||
|
#[inline(always)]
|
||||||
|
fn mark_did_receive(&mut self, counter: u64) -> Result<(), WireGuardError> {
|
||||||
|
if counter + N_BITS < self.next {
|
||||||
|
// Drop if too far back
|
||||||
|
return Err(WireGuardError::InvalidCounter)
|
||||||
|
}
|
||||||
|
if counter == self.next {
|
||||||
|
// Usually the packets arrive in order, in that case we simply mark the bit and
|
||||||
|
// increment the counter
|
||||||
|
self.set_bit(counter);
|
||||||
|
self.next += 1;
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if counter < self.next {
|
||||||
|
// A packet arrived out of order, check if it is valid, and mark
|
||||||
|
if self.check_bit(counter) {
|
||||||
|
return Err(WireGuardError::InvalidCounter)
|
||||||
|
}
|
||||||
|
self.set_bit(counter);
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
// Packets where dropped, or maybe reordered, skip them and mark unused
|
||||||
|
if counter - self.next >= N_BITS {
|
||||||
|
// Too far ahead, clear all the bits
|
||||||
|
for c in self.bitmap.iter_mut() {
|
||||||
|
*c = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut i = self.next;
|
||||||
|
while i % WORD_SIZE != 0 && i < counter {
|
||||||
|
// Clear until i aligned to word size
|
||||||
|
self.clear_bit(i);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
while i + WORD_SIZE < counter {
|
||||||
|
// Clear whole word at a time
|
||||||
|
self.clear_word(i);
|
||||||
|
i = (i + WORD_SIZE) & 0u64.wrapping_sub(WORD_SIZE);
|
||||||
|
}
|
||||||
|
while i < counter {
|
||||||
|
// Clear any remaining bits
|
||||||
|
self.clear_bit(i);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.set_bit(counter);
|
||||||
|
self.next = counter + 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub(super) fn new(
|
||||||
|
local_index: u32,
|
||||||
|
peer_index: u32,
|
||||||
|
receiving_key: [u8; 32],
|
||||||
|
sending_key: [u8; 32],
|
||||||
|
) -> Session {
|
||||||
|
Session {
|
||||||
|
receiving_index: local_index,
|
||||||
|
sending_index: peer_index,
|
||||||
|
receiver: LessSafeKey::new(
|
||||||
|
UnboundKey::new(&CHACHA20_POLY1305, &receiving_key).unwrap(),
|
||||||
|
),
|
||||||
|
sender: LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, &sending_key).unwrap()),
|
||||||
|
sending_key_counter: AtomicUsize::new(0),
|
||||||
|
receiving_key_counter: Mutex::new(Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn local_index(&self) -> usize {
|
||||||
|
self.receiving_index as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if receiving counter is good to use
|
||||||
|
fn receiving_counter_quick_check(&self, counter: u64) -> Result<(), WireGuardError> {
|
||||||
|
let counter_validator = self.receiving_key_counter.lock();
|
||||||
|
counter_validator.will_accept(counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if receiving counter is good to use, and marks it as used {
|
||||||
|
fn receiving_counter_mark(&self, counter: u64) -> Result<(), WireGuardError> {
|
||||||
|
let mut counter_validator = self.receiving_key_counter.lock();
|
||||||
|
let ret = counter_validator.mark_did_receive(counter);
|
||||||
|
if ret.is_ok() {
|
||||||
|
counter_validator.receive_cnt += 1;
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// src - an IP packet from the interface
|
||||||
|
/// dst - pre-allocated space to hold the encapsulating UDP packet to send
|
||||||
|
/// over the network returns the size of the formatted packet
|
||||||
|
pub(super) fn format_packet_data<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> &'a mut [u8] {
|
||||||
|
if dst.len() < src.len() + super::DATA_OVERHEAD_SZ {
|
||||||
|
panic!("The destination buffer is too small");
|
||||||
|
}
|
||||||
|
|
||||||
|
let sending_key_counter = self.sending_key_counter.fetch_add(1, Ordering::Relaxed) as u64;
|
||||||
|
|
||||||
|
let (message_type, rest) = dst.split_at_mut(4);
|
||||||
|
let (receiver_index, rest) = rest.split_at_mut(4);
|
||||||
|
let (counter, data) = rest.split_at_mut(8);
|
||||||
|
|
||||||
|
message_type.copy_from_slice(&super::DATA.to_le_bytes());
|
||||||
|
receiver_index.copy_from_slice(&self.sending_index.to_le_bytes());
|
||||||
|
counter.copy_from_slice(&sending_key_counter.to_le_bytes());
|
||||||
|
|
||||||
|
// TODO: spec requires padding to 16 bytes, but actually works fine without it
|
||||||
|
let n = {
|
||||||
|
let mut nonce = [0u8; 12];
|
||||||
|
nonce[4..12].copy_from_slice(&sending_key_counter.to_le_bytes());
|
||||||
|
data[..src.len()].copy_from_slice(src);
|
||||||
|
self.sender
|
||||||
|
.seal_in_place_separate_tag(
|
||||||
|
Nonce::assume_unique_for_key(nonce),
|
||||||
|
Aad::from(&[]),
|
||||||
|
&mut data[..src.len()],
|
||||||
|
)
|
||||||
|
.map(|tag| {
|
||||||
|
data[src.len()..src.len() + AEAD_SIZE].copy_from_slice(tag.as_ref());
|
||||||
|
src.len() + AEAD_SIZE
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
&mut dst[..DATA_OFFSET + n]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// packet - a data packet we received from the network
|
||||||
|
/// dst - pre-allocated space to hold the encapsulated IP packet, to send to
|
||||||
|
/// the interface dst will always take less space than src
|
||||||
|
/// return the size of the encapsulated packet on success
|
||||||
|
pub(super) fn receive_packet_data<'a>(
|
||||||
|
&self,
|
||||||
|
packet: PacketData,
|
||||||
|
dst: &'a mut [u8],
|
||||||
|
) -> Result<&'a mut [u8], WireGuardError> {
|
||||||
|
let ct_len = packet.encrypted_encapsulated_packet.len();
|
||||||
|
if dst.len() < ct_len {
|
||||||
|
// This is a very incorrect use of the library, therefore panic and not error
|
||||||
|
panic!("The destination buffer is too small");
|
||||||
|
}
|
||||||
|
if packet.receiver_idx != self.receiving_index {
|
||||||
|
return Err(WireGuardError::WrongIndex)
|
||||||
|
}
|
||||||
|
// Don't reuse counters, in case this is a replay attack we want to quickly
|
||||||
|
// check the counter without running expensive decryption
|
||||||
|
self.receiving_counter_quick_check(packet.counter)?;
|
||||||
|
|
||||||
|
tracing::debug!("TAG C");
|
||||||
|
let ret = {
|
||||||
|
let mut nonce = [0u8; 12];
|
||||||
|
nonce[4..12].copy_from_slice(&packet.counter.to_le_bytes());
|
||||||
|
dst[..ct_len].copy_from_slice(packet.encrypted_encapsulated_packet);
|
||||||
|
self.receiver
|
||||||
|
.open_in_place(
|
||||||
|
Nonce::assume_unique_for_key(nonce),
|
||||||
|
Aad::from(&[]),
|
||||||
|
&mut dst[..ct_len],
|
||||||
|
)
|
||||||
|
.map_err(|_| WireGuardError::InvalidAeadTag)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// After decryption is done, check counter again, and mark as received
|
||||||
|
self.receiving_counter_mark(packet.counter)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the estimated downstream packet loss for this session
|
||||||
|
pub(super) fn current_packet_cnt(&self) -> (u64, u64) {
|
||||||
|
let counter_validator = self.receiving_key_counter.lock();
|
||||||
|
(counter_validator.next, counter_validator.receive_cnt)
|
||||||
|
}
|
||||||
|
}
|
||||||
333
burrow/src/wireguard/noise/timers.rs
Executable file
333
burrow/src/wireguard/noise/timers.rs
Executable file
|
|
@ -0,0 +1,333 @@
|
||||||
|
// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
mem,
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{errors::WireGuardError, TunnResult, Tunnel};
|
||||||
|
|
||||||
|
// Some constants, represent time in seconds
|
||||||
|
// https://www.wireguard.com/papers/wireguard.pdf#page=14
|
||||||
|
pub(crate) const REKEY_AFTER_TIME: Duration = Duration::from_secs(120);
|
||||||
|
const REJECT_AFTER_TIME: Duration = Duration::from_secs(180);
|
||||||
|
const REKEY_ATTEMPT_TIME: Duration = Duration::from_secs(90);
|
||||||
|
pub(crate) const REKEY_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
const COOKIE_EXPIRATION_TIME: Duration = Duration::from_secs(120);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TimerName {
|
||||||
|
/// Current time, updated each call to `update_timers`
|
||||||
|
TimeCurrent,
|
||||||
|
/// Time when last handshake was completed
|
||||||
|
TimeSessionEstablished,
|
||||||
|
/// Time the last attempt for a new handshake began
|
||||||
|
TimeLastHandshakeStarted,
|
||||||
|
/// Time we last received and authenticated a packet
|
||||||
|
TimeLastPacketReceived,
|
||||||
|
/// Time we last send a packet
|
||||||
|
TimeLastPacketSent,
|
||||||
|
/// Time we last received and authenticated a DATA packet
|
||||||
|
TimeLastDataPacketReceived,
|
||||||
|
/// Time we last send a DATA packet
|
||||||
|
TimeLastDataPacketSent,
|
||||||
|
/// Time we last received a cookie
|
||||||
|
TimeCookieReceived,
|
||||||
|
/// Time we last sent persistent keepalive
|
||||||
|
TimePersistentKeepalive,
|
||||||
|
Top,
|
||||||
|
}
|
||||||
|
|
||||||
|
use self::TimerName::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timers {
|
||||||
|
/// Is the owner of the timer the initiator or the responder for the last
|
||||||
|
/// handshake?
|
||||||
|
is_initiator: bool,
|
||||||
|
/// Start time of the tunnel
|
||||||
|
time_started: Instant,
|
||||||
|
timers: [Duration; TimerName::Top as usize],
|
||||||
|
pub(super) session_timers: [Duration; super::N_SESSIONS],
|
||||||
|
/// Did we receive data without sending anything back?
|
||||||
|
want_keepalive: bool,
|
||||||
|
/// Did we send data without hearing back?
|
||||||
|
want_handshake: bool,
|
||||||
|
persistent_keepalive: usize,
|
||||||
|
/// Should this timer call reset rr function (if not a shared rr instance)
|
||||||
|
pub(super) should_reset_rr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timers {
|
||||||
|
pub(super) fn new(persistent_keepalive: Option<u16>, reset_rr: bool) -> Timers {
|
||||||
|
Timers {
|
||||||
|
is_initiator: false,
|
||||||
|
time_started: Instant::now(),
|
||||||
|
timers: Default::default(),
|
||||||
|
session_timers: Default::default(),
|
||||||
|
want_keepalive: Default::default(),
|
||||||
|
want_handshake: Default::default(),
|
||||||
|
persistent_keepalive: usize::from(persistent_keepalive.unwrap_or(0)),
|
||||||
|
should_reset_rr: reset_rr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initiator(&self) -> bool {
|
||||||
|
self.is_initiator
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't really clear the timers, but we set them to the current time to
|
||||||
|
// so the reference time frame is the same
|
||||||
|
pub(super) fn clear(&mut self) {
|
||||||
|
let now = Instant::now().duration_since(self.time_started);
|
||||||
|
for t in &mut self.timers[..] {
|
||||||
|
*t = now;
|
||||||
|
}
|
||||||
|
self.want_handshake = false;
|
||||||
|
self.want_keepalive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<TimerName> for Timers {
|
||||||
|
type Output = Duration;
|
||||||
|
|
||||||
|
fn index(&self, index: TimerName) -> &Duration {
|
||||||
|
&self.timers[index as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<TimerName> for Timers {
|
||||||
|
fn index_mut(&mut self, index: TimerName) -> &mut Duration {
|
||||||
|
&mut self.timers[index as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tunnel {
|
||||||
|
pub(super) fn timer_tick(&mut self, timer_name: TimerName) {
|
||||||
|
match timer_name {
|
||||||
|
TimeLastPacketReceived => {
|
||||||
|
self.timers.want_keepalive = true;
|
||||||
|
self.timers.want_handshake = false;
|
||||||
|
}
|
||||||
|
TimeLastPacketSent => {
|
||||||
|
self.timers.want_handshake = true;
|
||||||
|
self.timers.want_keepalive = false;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = self.timers[TimeCurrent];
|
||||||
|
self.timers[timer_name] = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn timer_tick_session_established(
|
||||||
|
&mut self,
|
||||||
|
is_initiator: bool,
|
||||||
|
session_idx: usize,
|
||||||
|
) {
|
||||||
|
self.timer_tick(TimeSessionEstablished);
|
||||||
|
self.timers.session_timers[session_idx % super::N_SESSIONS] = self.timers[TimeCurrent];
|
||||||
|
self.timers.is_initiator = is_initiator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't really clear the timers, but we set them to the current time to
|
||||||
|
// so the reference time frame is the same
|
||||||
|
fn clear_all(&mut self) {
|
||||||
|
for session in &mut self.sessions {
|
||||||
|
*session = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.packet_queue.clear();
|
||||||
|
|
||||||
|
self.timers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_session_timers(&mut self, time_now: Duration) {
|
||||||
|
let timers = &mut self.timers;
|
||||||
|
|
||||||
|
for (i, t) in timers.session_timers.iter_mut().enumerate() {
|
||||||
|
if time_now - *t > REJECT_AFTER_TIME {
|
||||||
|
if let Some(session) = self.sessions[i].take() {
|
||||||
|
tracing::debug!(
|
||||||
|
message = "SESSION_EXPIRED(REJECT_AFTER_TIME)",
|
||||||
|
session = session.receiving_index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*t = time_now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_timers<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> {
|
||||||
|
let mut handshake_initiation_required = false;
|
||||||
|
let mut keepalive_required = false;
|
||||||
|
|
||||||
|
let time = Instant::now();
|
||||||
|
|
||||||
|
if self.timers.should_reset_rr {
|
||||||
|
self.rate_limiter.reset_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the times are counted from tunnel initiation, for efficiency our timers
|
||||||
|
// are rounded to a second, as there is no real benefit to having highly
|
||||||
|
// accurate timers.
|
||||||
|
let now = time.duration_since(self.timers.time_started);
|
||||||
|
self.timers[TimeCurrent] = now;
|
||||||
|
|
||||||
|
self.update_session_timers(now);
|
||||||
|
|
||||||
|
// Load timers only once:
|
||||||
|
let session_established = self.timers[TimeSessionEstablished];
|
||||||
|
let handshake_started = self.timers[TimeLastHandshakeStarted];
|
||||||
|
let aut_packet_received = self.timers[TimeLastPacketReceived];
|
||||||
|
let aut_packet_sent = self.timers[TimeLastPacketSent];
|
||||||
|
let data_packet_received = self.timers[TimeLastDataPacketReceived];
|
||||||
|
let data_packet_sent = self.timers[TimeLastDataPacketSent];
|
||||||
|
let persistent_keepalive = self.timers.persistent_keepalive;
|
||||||
|
|
||||||
|
{
|
||||||
|
if self.handshake.is_expired() {
|
||||||
|
return TunnResult::Err(WireGuardError::ConnectionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cookie after COOKIE_EXPIRATION_TIME
|
||||||
|
if self.handshake.has_cookie()
|
||||||
|
&& now - self.timers[TimeCookieReceived] >= COOKIE_EXPIRATION_TIME
|
||||||
|
{
|
||||||
|
self.handshake.clear_cookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All ephemeral private keys and symmetric session keys are zeroed out after
|
||||||
|
// (REJECT_AFTER_TIME * 3) ms if no new keys have been exchanged.
|
||||||
|
if now - session_established >= REJECT_AFTER_TIME * 3 {
|
||||||
|
tracing::error!("CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)");
|
||||||
|
self.handshake.set_expired();
|
||||||
|
self.clear_all();
|
||||||
|
return TunnResult::Err(WireGuardError::ConnectionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(time_init_sent) = self.handshake.timer() {
|
||||||
|
// Handshake Initiation Retransmission
|
||||||
|
if now - handshake_started >= REKEY_ATTEMPT_TIME {
|
||||||
|
// After REKEY_ATTEMPT_TIME ms of trying to initiate a new handshake,
|
||||||
|
// the retries give up and cease, and clear all existing packets queued
|
||||||
|
// up to be sent. If a packet is explicitly queued up to be sent, then
|
||||||
|
// this timer is reset.
|
||||||
|
tracing::error!("CONNECTION_EXPIRED(REKEY_ATTEMPT_TIME)");
|
||||||
|
self.handshake.set_expired();
|
||||||
|
self.clear_all();
|
||||||
|
return TunnResult::Err(WireGuardError::ConnectionExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
if time_init_sent.elapsed() >= REKEY_TIMEOUT {
|
||||||
|
// We avoid using `time` here, because it can be earlier than `time_init_sent`.
|
||||||
|
// Once `checked_duration_since` is stable we can use that.
|
||||||
|
// A handshake initiation is retried after REKEY_TIMEOUT + jitter ms,
|
||||||
|
// if a response has not been received, where jitter is some random
|
||||||
|
// value between 0 and 333 ms.
|
||||||
|
tracing::warn!("HANDSHAKE(REKEY_TIMEOUT)");
|
||||||
|
handshake_initiation_required = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.timers.is_initiator() {
|
||||||
|
// After sending a packet, if the sender was the original initiator
|
||||||
|
// of the handshake and if the current session key is REKEY_AFTER_TIME
|
||||||
|
// ms old, we initiate a new handshake. If the sender was the original
|
||||||
|
// responder of the handshake, it does not re-initiate a new handshake
|
||||||
|
// after REKEY_AFTER_TIME ms like the original initiator does.
|
||||||
|
if session_established < data_packet_sent
|
||||||
|
&& now - session_established >= REKEY_AFTER_TIME
|
||||||
|
{
|
||||||
|
tracing::debug!("HANDSHAKE(REKEY_AFTER_TIME (on send))");
|
||||||
|
handshake_initiation_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After receiving a packet, if the receiver was the original initiator
|
||||||
|
// of the handshake and if the current session key is REJECT_AFTER_TIME
|
||||||
|
// - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT ms old, we initiate a new
|
||||||
|
// handshake.
|
||||||
|
if session_established < data_packet_received
|
||||||
|
&& now - session_established
|
||||||
|
>= REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT
|
||||||
|
{
|
||||||
|
tracing::warn!(
|
||||||
|
"HANDSHAKE(REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - \
|
||||||
|
REKEY_TIMEOUT \
|
||||||
|
(on receive))"
|
||||||
|
);
|
||||||
|
handshake_initiation_required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have sent a packet to a given peer but have not received a
|
||||||
|
// packet after from that peer for (KEEPALIVE + REKEY_TIMEOUT) ms,
|
||||||
|
// we initiate a new handshake.
|
||||||
|
if data_packet_sent > aut_packet_received
|
||||||
|
&& now - aut_packet_received >= KEEPALIVE_TIMEOUT + REKEY_TIMEOUT
|
||||||
|
&& mem::replace(&mut self.timers.want_handshake, false)
|
||||||
|
{
|
||||||
|
tracing::warn!("HANDSHAKE(KEEPALIVE + REKEY_TIMEOUT)");
|
||||||
|
handshake_initiation_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !handshake_initiation_required {
|
||||||
|
// If a packet has been received from a given peer, but we have not sent one
|
||||||
|
// back to the given peer in KEEPALIVE ms, we send an empty
|
||||||
|
// packet.
|
||||||
|
if data_packet_received > aut_packet_sent
|
||||||
|
&& now - aut_packet_sent >= KEEPALIVE_TIMEOUT
|
||||||
|
&& mem::replace(&mut self.timers.want_keepalive, false)
|
||||||
|
{
|
||||||
|
tracing::debug!("KEEPALIVE(KEEPALIVE_TIMEOUT)");
|
||||||
|
keepalive_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persistent KEEPALIVE
|
||||||
|
if persistent_keepalive > 0
|
||||||
|
&& (now - self.timers[TimePersistentKeepalive]
|
||||||
|
>= Duration::from_secs(persistent_keepalive as _))
|
||||||
|
{
|
||||||
|
tracing::debug!("KEEPALIVE(PERSISTENT_KEEPALIVE)");
|
||||||
|
self.timer_tick(TimePersistentKeepalive);
|
||||||
|
keepalive_required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handshake_initiation_required {
|
||||||
|
return self.format_handshake_initiation(dst, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keepalive_required {
|
||||||
|
return self.encapsulate(&[], dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
TunnResult::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time_since_last_handshake(&self) -> Option<Duration> {
|
||||||
|
let current_session = self.current;
|
||||||
|
if self.sessions[current_session % super::N_SESSIONS].is_some() {
|
||||||
|
let duration_since_tun_start = Instant::now().duration_since(self.timers.time_started);
|
||||||
|
let duration_since_session_established = self.timers[TimeSessionEstablished];
|
||||||
|
|
||||||
|
Some(duration_since_tun_start - duration_since_session_established)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persistent_keepalive(&self) -> Option<u16> {
|
||||||
|
let keepalive = self.timers.persistent_keepalive;
|
||||||
|
|
||||||
|
if keepalive > 0 {
|
||||||
|
Some(keepalive as u16)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
burrow/src/wireguard/pcb.rs
Executable file
135
burrow/src/wireguard/pcb.rs
Executable file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use fehler::throws;
|
||||||
|
use ip_network::IpNetwork;
|
||||||
|
use rand::random;
|
||||||
|
use tokio::{net::UdpSocket, sync::RwLock, task::JoinHandle};
|
||||||
|
use tun::tokio::TunInterface;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
noise::{TunnResult, Tunnel},
|
||||||
|
Peer,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeerPcb {
|
||||||
|
pub endpoint: SocketAddr,
|
||||||
|
pub allowed_ips: Vec<IpNetwork>,
|
||||||
|
pub handle: RwLock<Option<JoinHandle<()>>>,
|
||||||
|
socket: RwLock<Option<UdpSocket>>,
|
||||||
|
tunnel: RwLock<Tunnel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerPcb {
|
||||||
|
#[throws]
|
||||||
|
pub fn new(peer: Peer) -> Self {
|
||||||
|
let tunnel = RwLock::new(
|
||||||
|
Tunnel::new(
|
||||||
|
peer.private_key,
|
||||||
|
peer.public_key,
|
||||||
|
peer.preshared_key,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|s| anyhow::anyhow!("{}", s))?,
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
endpoint: peer.endpoint,
|
||||||
|
allowed_ips: peer.allowed_ips,
|
||||||
|
handle: RwLock::new(None),
|
||||||
|
socket: RwLock::new(None),
|
||||||
|
tunnel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open_if_closed(&self) -> Result<(), Error> {
|
||||||
|
if self.socket.read().await.is_none() {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
|
socket.connect(self.endpoint).await?;
|
||||||
|
self.socket.write().await.replace(socket);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self, tun_interface: Arc<RwLock<TunInterface>>) -> Result<(), Error> {
|
||||||
|
tracing::debug!("starting read loop for pcb... for {:?}", &self);
|
||||||
|
let rid: i32 = random();
|
||||||
|
let mut buf: [u8; 3000] = [0u8; 3000];
|
||||||
|
tracing::debug!("start read loop {}", rid);
|
||||||
|
loop {
|
||||||
|
tracing::debug!("{}: waiting for packet", rid);
|
||||||
|
let guard = self.socket.read().await;
|
||||||
|
let Some(socket) = guard.as_ref() else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let mut res_buf = [0; 1500];
|
||||||
|
// tracing::debug!("{} : waiting for readability on {:?}", rid, socket);
|
||||||
|
let len = match socket.recv(&mut res_buf).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}: error reading from socket: {:?}", rid, e);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut res_dat = &res_buf[..len];
|
||||||
|
tracing::debug!("{}: Decapsulating {} bytes", rid, len);
|
||||||
|
tracing::debug!("{:?}", &res_dat);
|
||||||
|
loop {
|
||||||
|
match self
|
||||||
|
.tunnel
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.decapsulate(None, res_dat, &mut buf[..])
|
||||||
|
{
|
||||||
|
TunnResult::Done => break,
|
||||||
|
TunnResult::Err(e) => {
|
||||||
|
tracing::error!(message = "Decapsulate error", error = ?e);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
TunnResult::WriteToNetwork(packet) => {
|
||||||
|
tracing::debug!("WriteToNetwork: {:?}", packet);
|
||||||
|
self.open_if_closed().await?;
|
||||||
|
socket.send(packet).await?;
|
||||||
|
tracing::debug!("WriteToNetwork done");
|
||||||
|
res_dat = &[];
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
TunnResult::WriteToTunnelV4(packet, addr) => {
|
||||||
|
tracing::debug!("WriteToTunnelV4: {:?}, {:?}", packet, addr);
|
||||||
|
tun_interface.read().await.send(packet).await?;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
TunnResult::WriteToTunnelV6(packet, addr) => {
|
||||||
|
tracing::debug!("WriteToTunnelV6: {:?}, {:?}", packet, addr);
|
||||||
|
tun_interface.read().await.send(packet).await?;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self, src: &[u8]) -> Result<(), Error> {
|
||||||
|
let mut dst_buf = [0u8; 3000];
|
||||||
|
match self.tunnel.write().await.encapsulate(src, &mut dst_buf[..]) {
|
||||||
|
TunnResult::Done => {}
|
||||||
|
TunnResult::Err(e) => {
|
||||||
|
tracing::error!(message = "Encapsulate error", error = ?e)
|
||||||
|
}
|
||||||
|
TunnResult::WriteToNetwork(packet) => {
|
||||||
|
self.open_if_closed().await?;
|
||||||
|
let handle = self.socket.read().await;
|
||||||
|
let Some(socket) = handle.as_ref() else {
|
||||||
|
tracing::error!("No socket for peer");
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
tracing::debug!("Our Encapsulated packet: {:?}", packet);
|
||||||
|
socket.send(packet).await?;
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected result from encapsulate"),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
22
burrow/src/wireguard/peer.rs
Executable file
22
burrow/src/wireguard/peer.rs
Executable file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::{fmt, net::SocketAddr};
|
||||||
|
|
||||||
|
use ip_network::IpNetwork;
|
||||||
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
pub struct Peer {
|
||||||
|
pub endpoint: SocketAddr,
|
||||||
|
pub private_key: StaticSecret,
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
pub allowed_ips: Vec<IpNetwork>,
|
||||||
|
pub preshared_key: Option<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Peer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Peer")
|
||||||
|
.field("endpoint", &self.endpoint)
|
||||||
|
.field("public_key", &self.public_key)
|
||||||
|
.field("allowed_ips", &self.allowed_ips)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,5 +39,5 @@ anyhow = "1.0"
|
||||||
bindgen = "0.65"
|
bindgen = "0.65"
|
||||||
reqwest = { version = "0.11", features = ["native-tls"] }
|
reqwest = { version = "0.11", features = ["native-tls"] }
|
||||||
ssri = { version = "9.0", default-features = false }
|
ssri = { version = "9.0", default-features = false }
|
||||||
tokio = { version = "1.28", features = ["rt"] }
|
tokio = { version = "1.28", features = ["rt", "macros"] }
|
||||||
zip = { version = "0.6", features = ["deflate"] }
|
zip = { version = "0.6", features = ["deflate"] }
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ async fn generate(out_dir: &std::path::Path) -> anyhow::Result<()> {
|
||||||
println!("cargo:rerun-if-changed={}", binary_path.to_str().unwrap());
|
println!("cargo:rerun-if-changed={}", binary_path.to_str().unwrap());
|
||||||
|
|
||||||
if let (Ok(..), Ok(..)) = (File::open(&bindings_path), File::open(&binary_path)) {
|
if let (Ok(..), Ok(..)) = (File::open(&bindings_path), File::open(&binary_path)) {
|
||||||
return Ok(());
|
return Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let archive = download(out_dir)
|
let archive = download(out_dir)
|
||||||
|
|
@ -80,9 +80,10 @@ async fn download(directory: &std::path::Path) -> anyhow::Result<std::fs::File>
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn parse(file: std::fs::File) -> anyhow::Result<(bindgen::Bindings, Vec<u8>)> {
|
fn parse(file: std::fs::File) -> anyhow::Result<(bindgen::Bindings, Vec<u8>)> {
|
||||||
use anyhow::Context;
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
let reader = std::io::BufReader::new(file);
|
let reader = std::io::BufReader::new(file);
|
||||||
let mut archive = zip::ZipArchive::new(reader)?;
|
let mut archive = zip::ZipArchive::new(reader)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[path = "windows/mod.rs"]
|
#[path = "windows/mod.rs"]
|
||||||
mod imp;
|
mod os_imp;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
#[path = "unix/mod.rs"]
|
#[path = "unix/mod.rs"]
|
||||||
pub(crate) mod imp;
|
pub(crate) mod os_imp;
|
||||||
|
|
||||||
mod options;
|
mod options;
|
||||||
|
|
||||||
|
|
@ -14,5 +14,5 @@ mod options;
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub mod tokio;
|
pub mod tokio;
|
||||||
|
|
||||||
pub use imp::{TunInterface, TunQueue};
|
|
||||||
pub use options::TunOptions;
|
pub use options::TunOptions;
|
||||||
|
pub use os_imp::{TunInterface, TunQueue};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,27 @@
|
||||||
use fehler::throws;
|
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
|
|
||||||
use super::TunInterface;
|
use fehler::throws;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
use super::tokio::TunInterface;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema))]
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)
|
||||||
|
)]
|
||||||
pub struct TunOptions {
|
pub struct TunOptions {
|
||||||
/// (Windows + Linux) Name the tun interface.
|
/// (Windows + Linux) Name the tun interface.
|
||||||
pub(crate) name: Option<String>,
|
pub name: Option<String>,
|
||||||
/// (Linux) Don't include packet information.
|
/// (Linux) Don't include packet information.
|
||||||
pub(crate) no_pi: Option<()>,
|
pub no_pi: bool,
|
||||||
/// (Linux) Avoid opening an existing persistant device.
|
/// (Linux) Avoid opening an existing persistant device.
|
||||||
pub(crate) tun_excl: Option<()>,
|
pub tun_excl: bool,
|
||||||
|
/// (Apple) Retrieve the tun interface
|
||||||
|
pub tun_retrieve: bool,
|
||||||
|
/// (Linux) The IP address of the tun interface.
|
||||||
|
pub address: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunOptions {
|
impl TunOptions {
|
||||||
|
|
@ -24,16 +34,26 @@ impl TunOptions {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_pi(mut self, enable: bool) {
|
pub fn no_pi(mut self, enable: bool) -> Self {
|
||||||
self.no_pi = enable.then_some(());
|
self.no_pi = enable;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tun_excl(mut self, enable: bool) {
|
pub fn tun_excl(mut self, enable: bool) -> Self {
|
||||||
self.tun_excl = enable.then_some(());
|
self.tun_excl = enable;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn address(mut self, address: impl ToString) -> Self {
|
||||||
|
self.address = Some(address.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
#[throws]
|
#[throws]
|
||||||
pub fn open(self) -> TunInterface {
|
pub fn open(self) -> TunInterface {
|
||||||
TunInterface::new_with_options(self)?
|
let ti = super::TunInterface::new_with_options(self)?;
|
||||||
|
TunInterface::new(ti)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use tokio::io::unix::AsyncFd;
|
use tokio::io::unix::AsyncFd;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TunInterface {
|
pub struct TunInterface {
|
||||||
inner: AsyncFd<crate::TunInterface>,
|
pub inner: AsyncFd<crate::TunInterface>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunInterface {
|
impl TunInterface {
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub fn new(tun: crate::TunInterface) -> io::Result<Self> {
|
pub fn new(mut tun: crate::TunInterface) -> io::Result<Self> {
|
||||||
Ok(Self {
|
tun.set_nonblocking(true)?;
|
||||||
inner: AsyncFd::new(tun)?,
|
Ok(Self { inner: AsyncFd::new(tun)? })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
pub async fn send(&self, buf: &[u8]) -> io::Result<usize> {
|
||||||
loop {
|
loop {
|
||||||
let mut guard = self.inner.writable().await?;
|
let mut guard = self.inner.writable().await?;
|
||||||
match guard.try_io(|inner| inner.get_ref().send(buf)) {
|
match guard.try_io(|inner| inner.get_ref().send(buf)) {
|
||||||
|
|
@ -27,12 +27,15 @@ impl TunInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
pub async fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
loop {
|
loop {
|
||||||
let mut guard = self.inner.readable_mut().await?;
|
let mut guard = self.inner.readable().await?;
|
||||||
match guard.try_io(|inner| (*inner).get_mut().recv(buf)) {
|
match guard.try_io(|inner| inner.get_ref().recv(buf)) {
|
||||||
Ok(result) => return result,
|
Ok(result) => return result,
|
||||||
Err(_would_block) => continue,
|
Err(_would_block) => {
|
||||||
|
tracing::debug!("WouldBlock");
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
use std::{io::Error, mem::size_of, os::unix::io::AsRawFd};
|
||||||
|
|
||||||
use fehler::throws;
|
use fehler::throws;
|
||||||
use std::io::Error;
|
|
||||||
use std::mem::size_of;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use super::sys;
|
use super::sys;
|
||||||
|
|
||||||
|
|
@ -16,10 +15,7 @@ pub trait SysControlSocket {
|
||||||
impl SysControlSocket for socket2::Socket {
|
impl SysControlSocket for socket2::Socket {
|
||||||
#[throws]
|
#[throws]
|
||||||
fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr {
|
fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr {
|
||||||
let mut info = sys::ctl_info {
|
let mut info = sys::ctl_info { ctl_id: 0, ctl_name: [0; 96] };
|
||||||
ctl_id: 0,
|
|
||||||
ctl_name: [0; 96],
|
|
||||||
};
|
|
||||||
info.ctl_name[..name.len()].copy_from_slice(name.as_bytes());
|
info.ctl_name[..name.len()].copy_from_slice(name.as_bytes());
|
||||||
|
|
||||||
unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? };
|
unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? };
|
||||||
|
|
@ -28,7 +24,7 @@ impl SysControlSocket for socket2::Socket {
|
||||||
socket2::SockAddr::init(|addr_storage, len| {
|
socket2::SockAddr::init(|addr_storage, len| {
|
||||||
*len = size_of::<sys::sockaddr_ctl>() as u32;
|
*len = size_of::<sys::sockaddr_ctl>() as u32;
|
||||||
|
|
||||||
let mut addr: &mut sys::sockaddr_ctl = &mut *addr_storage.cast();
|
let addr: &mut sys::sockaddr_ctl = &mut *addr_storage.cast();
|
||||||
addr.sc_len = *len as u8;
|
addr.sc_len = *len as u8;
|
||||||
addr.sc_family = sys::AF_SYSTEM as u8;
|
addr.sc_family = sys::AF_SYSTEM as u8;
|
||||||
addr.ss_sysaddr = sys::AF_SYS_CONTROL as u16;
|
addr.ss_sysaddr = sys::AF_SYS_CONTROL as u16;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
|
use std::{
|
||||||
|
io::{Error, IoSlice},
|
||||||
|
mem,
|
||||||
|
net::{Ipv4Addr, SocketAddrV4},
|
||||||
|
os::fd::{AsRawFd, FromRawFd, RawFd},
|
||||||
|
};
|
||||||
|
|
||||||
use byteorder::{ByteOrder, NetworkEndian};
|
use byteorder::{ByteOrder, NetworkEndian};
|
||||||
use fehler::throws;
|
use fehler::throws;
|
||||||
use libc::{c_char, iovec, writev, AF_INET, AF_INET6};
|
use libc::{c_char, iovec, writev, AF_INET, AF_INET6};
|
||||||
use tracing::info;
|
|
||||||
use socket2::{Domain, SockAddr, Socket, Type};
|
use socket2::{Domain, SockAddr, Socket, Type};
|
||||||
use std::io::IoSlice;
|
use tracing::{self, instrument};
|
||||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
|
||||||
use std::os::fd::{AsRawFd, RawFd};
|
|
||||||
use std::{io::Error, mem};
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
mod kern_control;
|
pub mod kern_control;
|
||||||
mod sys;
|
pub mod sys;
|
||||||
|
|
||||||
|
use kern_control::SysControlSocket;
|
||||||
|
|
||||||
pub use super::queue::TunQueue;
|
pub use super::queue::TunQueue;
|
||||||
|
use super::{ifname_to_string, string_to_ifname};
|
||||||
use super::{ifname_to_string, string_to_ifname, TunOptions};
|
use crate::TunOptions;
|
||||||
use kern_control::SysControlSocket;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TunInterface {
|
pub struct TunInterface {
|
||||||
|
|
@ -31,8 +34,49 @@ impl TunInterface {
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub fn new_with_options(_: TunOptions) -> TunInterface {
|
pub fn new_with_options(options: TunOptions) -> TunInterface {
|
||||||
|
let ti = if options.tun_retrieve {
|
||||||
|
TunInterface::retrieve().ok_or(Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
"No tun interface found",
|
||||||
|
))?
|
||||||
|
} else {
|
||||||
TunInterface::connect(0)?
|
TunInterface::connect(0)?
|
||||||
|
};
|
||||||
|
ti.configure(options)?;
|
||||||
|
ti
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retrieve() -> Option<TunInterface> {
|
||||||
|
(3..100)
|
||||||
|
.filter_map(|fd| unsafe {
|
||||||
|
let peer_addr = socket2::SockAddr::init(|storage, len| {
|
||||||
|
*len = mem::size_of::<sys::sockaddr_ctl>() as u32;
|
||||||
|
libc::getpeername(fd, storage as *mut _, len);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map(|(_, addr)| (fd, addr));
|
||||||
|
peer_addr.ok()
|
||||||
|
})
|
||||||
|
.filter(|(_fd, addr)| {
|
||||||
|
let ctl_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_ctl) };
|
||||||
|
addr.family() == libc::AF_SYSTEM as u8
|
||||||
|
&& ctl_addr.ss_sysaddr == libc::AF_SYS_CONTROL as u16
|
||||||
|
})
|
||||||
|
.map(|(fd, _)| {
|
||||||
|
let socket = unsafe { socket2::Socket::from_raw_fd(fd) };
|
||||||
|
TunInterface { socket }
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn configure(&self, options: TunOptions) {
|
||||||
|
if let Some(addr) = options.address {
|
||||||
|
if let Ok(addr) = addr.parse() {
|
||||||
|
self.set_ipv4_addr(addr)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
|
|
@ -81,7 +125,7 @@ impl TunInterface {
|
||||||
let mut iff = self.ifreq()?;
|
let mut iff = self.ifreq()?;
|
||||||
iff.ifr_ifru.ifru_addr = unsafe { *addr.as_ptr() };
|
iff.ifr_ifru.ifru_addr = unsafe { *addr.as_ptr() };
|
||||||
self.perform(|fd| unsafe { sys::if_set_addr(fd, &iff) })?;
|
self.perform(|fd| unsafe { sys::if_set_addr(fd, &iff) })?;
|
||||||
info!("ipv4_addr_set: {:?} (fd: {:?})", addr, self.as_raw_fd())
|
tracing::info!("ipv4_addr_set: {:?} (fd: {:?})", addr, self.as_raw_fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
|
|
@ -118,7 +162,7 @@ impl TunInterface {
|
||||||
let mut iff = self.ifreq()?;
|
let mut iff = self.ifreq()?;
|
||||||
iff.ifr_ifru.ifru_mtu = mtu;
|
iff.ifr_ifru.ifru_mtu = mtu;
|
||||||
self.perform(|fd| unsafe { sys::if_set_mtu(fd, &iff) })?;
|
self.perform(|fd| unsafe { sys::if_set_mtu(fd, &iff) })?;
|
||||||
info!("mtu_set: {:?} (fd: {:?})", mtu, self.as_raw_fd())
|
tracing::info!("mtu_set: {:?} (fd: {:?})", mtu, self.as_raw_fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
|
|
@ -140,7 +184,7 @@ impl TunInterface {
|
||||||
let mut iff = self.ifreq()?;
|
let mut iff = self.ifreq()?;
|
||||||
iff.ifr_ifru.ifru_netmask = unsafe { *addr.as_ptr() };
|
iff.ifr_ifru.ifru_netmask = unsafe { *addr.as_ptr() };
|
||||||
self.perform(|fd| unsafe { sys::if_set_netmask(fd, &iff) })?;
|
self.perform(|fd| unsafe { sys::if_set_netmask(fd, &iff) })?;
|
||||||
info!(
|
tracing::info!(
|
||||||
"netmask_set: {:?} (fd: {:?})",
|
"netmask_set: {:?} (fd: {:?})",
|
||||||
unsafe { iff.ifr_ifru.ifru_netmask },
|
unsafe { iff.ifr_ifru.ifru_netmask },
|
||||||
self.as_raw_fd()
|
self.as_raw_fd()
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,20 @@ use std::mem;
|
||||||
|
|
||||||
use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr};
|
use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr};
|
||||||
pub use libc::{
|
pub use libc::{
|
||||||
c_void, sockaddr_ctl, sockaddr_in, socklen_t, AF_SYSTEM, AF_SYS_CONTROL, IFNAMSIZ,
|
c_void,
|
||||||
|
sockaddr_ctl,
|
||||||
|
sockaddr_in,
|
||||||
|
socklen_t,
|
||||||
|
AF_SYSTEM,
|
||||||
|
AF_SYS_CONTROL,
|
||||||
|
IFNAMSIZ,
|
||||||
SYSPROTO_CONTROL,
|
SYSPROTO_CONTROL,
|
||||||
};
|
};
|
||||||
use nix::{
|
use nix::{
|
||||||
ioctl_read_bad, ioctl_readwrite, ioctl_write_ptr_bad, request_code_readwrite,
|
ioctl_read_bad,
|
||||||
|
ioctl_readwrite,
|
||||||
|
ioctl_write_ptr_bad,
|
||||||
|
request_code_readwrite,
|
||||||
request_code_write,
|
request_code_write,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
|
use std::{
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::{Error, Write},
|
||||||
|
mem,
|
||||||
|
net::{Ipv4Addr, Ipv6Addr, SocketAddrV4},
|
||||||
|
os::{
|
||||||
|
fd::RawFd,
|
||||||
|
unix::io::{AsRawFd, FromRawFd, IntoRawFd},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use fehler::throws;
|
use fehler::throws;
|
||||||
|
use libc::in6_ifreq;
|
||||||
use socket2::{Domain, SockAddr, Socket, Type};
|
use socket2::{Domain, SockAddr, Socket, Type};
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::{Error, Write};
|
|
||||||
use std::mem;
|
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
|
|
||||||
use std::os::fd::RawFd;
|
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
|
||||||
|
|
||||||
use tracing::{info, instrument};
|
use tracing::{info, instrument};
|
||||||
|
|
||||||
use libc::in6_ifreq;
|
use super::{ifname_to_string, string_to_ifname};
|
||||||
|
use crate::TunOptions;
|
||||||
use super::{ifname_to_string, string_to_ifname, TunOptions};
|
|
||||||
|
|
||||||
mod sys;
|
mod sys;
|
||||||
|
|
||||||
|
|
@ -38,10 +41,10 @@ impl TunInterface {
|
||||||
|
|
||||||
let mut flags = libc::IFF_TUN as i16;
|
let mut flags = libc::IFF_TUN as i16;
|
||||||
|
|
||||||
if options.no_pi.is_some() {
|
if options.no_pi {
|
||||||
flags |= libc::IFF_NO_PI as i16;
|
flags |= libc::IFF_NO_PI as i16;
|
||||||
}
|
}
|
||||||
if options.tun_excl.is_some() {
|
if options.tun_excl {
|
||||||
flags |= libc::IFF_TUN_EXCL as i16;
|
flags |= libc::IFF_TUN_EXCL as i16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub use libc::ifreq;
|
pub use libc::{ifreq, sockaddr, sockaddr_in, sockaddr_in6};
|
||||||
pub use libc::sockaddr;
|
use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write};
|
||||||
pub use libc::sockaddr_in;
|
|
||||||
pub use libc::sockaddr_in6;
|
|
||||||
|
|
||||||
ioctl_write_ptr_bad!(
|
ioctl_write_ptr_bad!(
|
||||||
tun_set_iff,
|
tun_set_iff,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{Error, Read},
|
io::Error,
|
||||||
|
mem::MaybeUninit,
|
||||||
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||||
};
|
};
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
use super::TunOptions;
|
use tracing::instrument;
|
||||||
|
|
||||||
mod queue;
|
mod queue;
|
||||||
|
|
||||||
|
|
@ -28,9 +28,8 @@ impl AsRawFd for TunInterface {
|
||||||
|
|
||||||
impl FromRawFd for TunInterface {
|
impl FromRawFd for TunInterface {
|
||||||
unsafe fn from_raw_fd(fd: RawFd) -> TunInterface {
|
unsafe fn from_raw_fd(fd: RawFd) -> TunInterface {
|
||||||
TunInterface {
|
let socket = socket2::Socket::from_raw_fd(fd);
|
||||||
socket: socket2::Socket::from_raw_fd(fd),
|
TunInterface { socket }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,11 +39,26 @@ impl IntoRawFd for TunInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn assume_init(buf: &[MaybeUninit<u8>]) -> &[u8] {
|
||||||
|
&*(buf as *const [MaybeUninit<u8>] as *const [u8])
|
||||||
|
}
|
||||||
|
|
||||||
impl TunInterface {
|
impl TunInterface {
|
||||||
#[throws]
|
#[throws]
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub fn recv(&mut self, buf: &mut [u8]) -> usize {
|
pub fn recv(&self, buf: &mut [u8]) -> usize {
|
||||||
self.socket.read(buf)?
|
// Use IoVec to read directly into target buffer
|
||||||
|
let mut tmp_buf = [MaybeUninit::uninit(); 1500];
|
||||||
|
let len = self.socket.recv(&mut tmp_buf)?;
|
||||||
|
let result_buf = unsafe { assume_init(&tmp_buf[4..len]) };
|
||||||
|
buf[..len - 4].copy_from_slice(result_buf);
|
||||||
|
len - 4
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
#[instrument]
|
||||||
|
pub fn set_nonblocking(&mut self, nb: bool) {
|
||||||
|
self.socket.set_nonblocking(nb)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use fehler::throws;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{Error, Read, Write},
|
io::{Error, Read, Write},
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
os::unix::io::{AsRawFd, IntoRawFd, RawFd},
|
os::unix::io::{AsRawFd, IntoRawFd, RawFd},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fehler::throws;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::TunInterface;
|
use crate::TunInterface;
|
||||||
|
|
@ -15,10 +15,9 @@ pub struct TunQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunQueue {
|
impl TunQueue {
|
||||||
#[throws]
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub fn recv(&self, buf: &mut [MaybeUninit<u8>]) -> usize {
|
pub fn recv(&self, buf: &mut [MaybeUninit<u8>]) -> Result<usize, Error> {
|
||||||
self.socket.recv(buf)?
|
self.socket.recv(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,9 +42,7 @@ impl Write for TunQueue {
|
||||||
|
|
||||||
impl From<TunInterface> for TunQueue {
|
impl From<TunInterface> for TunQueue {
|
||||||
fn from(interface: TunInterface) -> TunQueue {
|
fn from(interface: TunInterface) -> TunQueue {
|
||||||
TunQueue {
|
TunQueue { socket: interface.socket }
|
||||||
socket: interface.socket,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use std::fmt::Debug;
|
use std::{fmt::Debug, io::Error, ptr};
|
||||||
|
|
||||||
use fehler::throws;
|
use fehler::throws;
|
||||||
use std::io::Error;
|
|
||||||
use std::ptr;
|
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
use windows::Win32::Foundation::GetLastError;
|
use windows::Win32::Foundation::GetLastError;
|
||||||
mod queue;
|
mod queue;
|
||||||
|
|
||||||
use super::TunOptions;
|
|
||||||
|
|
||||||
pub use queue::TunQueue;
|
pub use queue::TunQueue;
|
||||||
|
|
||||||
|
use super::TunOptions;
|
||||||
|
|
||||||
pub struct TunInterface {
|
pub struct TunInterface {
|
||||||
handle: sys::WINTUN_ADAPTER_HANDLE,
|
handle: sys::WINTUN_ADAPTER_HANDLE,
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -40,10 +39,7 @@ impl TunInterface {
|
||||||
if handle.is_null() {
|
if handle.is_null() {
|
||||||
unsafe { GetLastError() }.ok()?
|
unsafe { GetLastError() }.ok()?
|
||||||
}
|
}
|
||||||
TunInterface {
|
TunInterface { handle, name: name_owned }
|
||||||
handle,
|
|
||||||
name: name_owned,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use std::{io::Error, net::Ipv4Addr};
|
||||||
|
|
||||||
use fehler::throws;
|
use fehler::throws;
|
||||||
use std::io::Error;
|
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
use tun::TunInterface;
|
use tun::TunInterface;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use fehler::throws;
|
use std::{io::Error, net::Ipv4Addr};
|
||||||
use std::io::Error;
|
|
||||||
|
|
||||||
use std::net::Ipv4Addr;
|
use fehler::throws;
|
||||||
use tun::TunInterface;
|
use tun::TunInterface;
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
|
|
@ -9,10 +8,10 @@ use tun::TunInterface;
|
||||||
#[ignore = "requires interactivity"]
|
#[ignore = "requires interactivity"]
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn tst_read() {
|
fn tst_read() {
|
||||||
// This test is interactive, you need to send a packet to any server through 192.168.1.10
|
// This test is interactive, you need to send a packet to any server through
|
||||||
// EG. `sudo route add 8.8.8.8 192.168.1.10`,
|
// 192.168.1.10 EG. `sudo route add 8.8.8.8 192.168.1.10`,
|
||||||
//`dig @8.8.8.8 hackclub.com`
|
//`dig @8.8.8.8 hackclub.com`
|
||||||
let mut tun = TunInterface::new()?;
|
let tun = TunInterface::new()?;
|
||||||
println!("tun name: {:?}", tun.name()?);
|
println!("tun name: {:?}", tun.name()?);
|
||||||
tun.set_ipv4_addr(Ipv4Addr::from([192, 168, 1, 10]))?;
|
tun.set_ipv4_addr(Ipv4Addr::from([192, 168, 1, 10]))?;
|
||||||
println!("tun ip: {:?}", tun.ipv4_addr()?);
|
println!("tun ip: {:?}", tun.ipv4_addr()?);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::net::Ipv4Addr;
|
||||||
#[cfg(all(feature = "tokio", not(target_os = "windows")))]
|
#[cfg(all(feature = "tokio", not(target_os = "windows")))]
|
||||||
async fn test_create() {
|
async fn test_create() {
|
||||||
let tun = tun::TunInterface::new().unwrap();
|
let tun = tun::TunInterface::new().unwrap();
|
||||||
let async_tun = tun::tokio::TunInterface::new(tun).unwrap();
|
let _ = tun::tokio::TunInterface::new(tun).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -17,6 +17,6 @@ async fn test_write() {
|
||||||
let async_tun = tun::tokio::TunInterface::new(tun).unwrap();
|
let async_tun = tun::tokio::TunInterface::new(tun).unwrap();
|
||||||
let mut buf = [0u8; 1500];
|
let mut buf = [0u8; 1500];
|
||||||
buf[0] = 6 << 4;
|
buf[0] = 6 << 4;
|
||||||
let bytes_written = async_tun.write(&buf).await.unwrap();
|
let bytes_written = async_tun.send(&buf).await.unwrap();
|
||||||
assert!(bytes_written > 0);
|
assert!(bytes_written > 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue