Merge pull request 'Remove jsonrpc from peach-network' (#46) from split_network_logic into main

Reviewed-on: #46
This commit is contained in:
glyph 2021-12-14 18:55:16 +00:00
commit 4d6dbd511e
9 changed files with 562 additions and 1784 deletions

31
Cargo.lock generated
View File

@ -2125,20 +2125,6 @@ dependencies = [
"void",
]
[[package]]
name = "nix"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7fd5681d13fda646462cfbd4e5f2051279a89a544d50eb98c365b507246839f"
dependencies = [
"bitflags 1.3.2",
"bytes 0.4.12",
"cfg-if 0.1.10",
"gcc",
"libc",
"void",
]
[[package]]
name = "nix"
version = "0.11.1"
@ -2546,20 +2532,13 @@ dependencies = [
[[package]]
name = "peach-network"
version = "0.2.12"
version = "0.3.0"
dependencies = [
"env_logger 0.6.2",
"failure",
"get_if_addrs",
"jsonrpc-core 11.0.0",
"jsonrpc-http-server 11.0.0",
"jsonrpc-test 11.0.0",
"log 0.4.14",
"miniserde",
"probes 0.4.1",
"regex",
"serde 1.0.130",
"serde_json",
"snafu 0.6.10",
"wpactrl",
]
@ -4773,12 +4752,10 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "wpactrl"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f487ac0a84d67974aa9ca3a7a284cb4821869d677376768f5c303fa786635c13"
source = "git+https://github.com/sauyon/wpa-ctrl-rs.git?branch=master#e37eeb0a9b58cde12bc9825515caaaadcbf49fa6"
dependencies = [
"failure",
"libc",
"log 0.4.14",
"nix 0.10.0",
]
[[package]]

View File

@ -1,44 +1,32 @@
[package]
name = "peach-network"
version = "0.2.12"
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
version = "0.3.0"
authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018"
description = "Query and configure network interfaces using JSON-RPC over HTTP."
description = "Query and configure network interfaces."
homepage = "https://opencollective.com/peachcloud"
repository = "https://github.com/peachcloud/peach-network"
repository = "ihttps://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-network"
readme = "README.md"
license = "AGPL-3.0-only"
license = "LGPL-3.0-only"
publish = false
[package.metadata.deb]
depends = "$auto"
extended-description = """\
peach-network is a microservice to query and configure network interfaces \
using JSON-RPC over HTTP."""
maintainer-scripts="debian"
systemd-units = { unit-name = "peach-network" }
assets = [
["target/release/peach-network", "usr/bin/", "755"],
["README.md", "usr/share/doc/peach-network/README", "644"],
]
[badges]
travis-ci = { repository = "peachcloud/peach-network", branch = "master" }
maintenance = { status = "actively-developed" }
[dependencies]
env_logger = "0.6"
failure = "0.1"
get_if_addrs = "0.5.3"
jsonrpc-core = "11"
jsonrpc-http-server = "11"
log = "0.4"
probes = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
snafu = "0.6"
miniserde = { version = "0.1.15", optional = true }
probes = "0.4.1"
serde = { version = "1.0.130", features = ["derive"], optional = true }
regex = "1"
wpactrl = "0.3.1"
# replace this with crate import once latest changes have been published
wpactrl = { git = "https://github.com/sauyon/wpa-ctrl-rs.git", branch = "master" }
[dev-dependencies]
jsonrpc-test = "11"
[features]
default = []
# Provide `Serialize` and `Deserialize` traits for library structs using `miniserde`
miniserde_support = ["miniserde"]
# Provide `Serialize` and `Deserialize` traits for library structs using `serde`
serde_support = ["serde"]

View File

@ -1,178 +1,42 @@
# peach-network
[![Build Status](https://travis-ci.com/peachcloud/peach-network.svg?branch=master)](https://travis-ci.com/peachcloud/peach-network) ![Generic badge](https://img.shields.io/badge/version-0.2.12-<COLOR>.svg)
![Generic badge](https://img.shields.io/badge/version-0.3.0-<COLOR>.svg)
Networking microservice module for PeachCloud. Query and configure device interfaces using [JSON-RPC](https://www.jsonrpc.org/specification) over http.
Network interface state query and modification library.
Interaction with wireless interfaces occurs primarily through the [wpactrl crate](https://docs.rs/wpactrl/0.3.1/wpactrl/) which provides "a pure-Rust lowlevel library for controlling wpasupplicant remotely". This approach is akin to using `wpa_cli` (a WPA command line client).
_Note: This module is a work-in-progress._
## API Documentation
### JSON-RPC API
API documentation can be built and served with `cargo doc --no-deps --open`. The full set of available data structures and functions is listed in the `peach_network::network` module. A custom error type (`NetworkError`) is also publically exposed for library users; it encapsulates all possible error variants.
Methods for **retrieving data**:
## Example Usage
| Method | Parameters | Description |
| --- | --- | --- |
| `available_networks` | `iface` | List SSID, flags (security), frequency and signal level for all networks in range of given interface |
| `id` | `iface`, `ssid` | Return ID of given SSID |
| `ip` | `iface` | Return IP of given network interface |
| `ping` | | Respond with `success` if microservice is running |
| `rssi` | `iface` | Return average signal strength (dBm) for given interface |
| `rssi_percent` | `iface` | Return average signal strength (%) for given interface |
| `saved_networks` | | List all networks saved in wpasupplicant config |
| `ssid` | `iface` | Return SSID of currently-connected network for given interface |
| `state` | `iface` | Return state of given interface |
| `status` | `iface` | Return status parameters for given interface |
| `traffic` | `iface` | Return network traffic for given interface |
```rust
use peach_network::{network, NetworkError};
Methods for **modifying state**:
fn main() -> Result<(), NetworkError> {
let ip = network::ip("wlan0")?;
let ssid = network::ssid("wlan0")?;
| Method | Parameters | Description |
| --- | --- | --- |
| `activate_ap` | | Activate WiFi access point (start `wpa_supplicant@ap0.service`) |
| `activate_client` | | Activate WiFi client connection (start `wpa_supplicant@wlan0.service`) |
| `add` | `ssid`, `pass` | Add WiFi credentials to `wpa_supplicant-wlan0.conf` |
| `check_iface` | | Activate WiFi access point if client mode is active without a connection |
| `connect` | `id`, `iface` | Disable other networks and attempt connection with AP represented by given id |
| `delete` | `id`, `iface` | Remove WiFi credentials for given network id and interface |
| `disable` | `id`, `iface` | Disable connection with AP represented by given id |
| `disconnect` | `iface` | Disconnect given interface |
| `modify` | `id`, `iface`, `password` | Set a new password for given network id and interface |
| `reassociate` | `iface` | Reassociate with current AP for given interface |
| `reconfigure` | | Force wpa_supplicant to re-read its configuration file |
| `reconnect` | `iface` | Disconnect and reconnect given interface |
| `save` | | Save configuration changes to `wpa_supplicant-wlan0.conf` |
let new_ap = Wifi { ssid: "Home".to_string(), pass: "SuperSecret".to_string() };
network::add(new_ap)?;
network::save()?;
### API Documentation
Ok(())
}
```
API documentation can be built and served with `cargo doc --no-deps --open`. This set of documentation is intended for developers who wish to work on the project or better understand the API of the `src/network.rs` module.
## Feature Flags
### Environment
Feature flags are used to offer `Serialize` and `Deserialize` implementations for all `struct` data types provided by this library. These traits are not provided by default. A choice of `miniserde` and `serde` is provided.
The JSON-RPC HTTP server address and port can be configured with the `PEACH_NETWORK_SERVER` environment variable:
Define the desired feature in the `Cargo.toml` manifest of your project:
`export PEACH_NETWORK_SERVER=127.0.0.1:5000`
```toml
peach-network = { version = "0.3.0", features = ["miniserde_support"] }
```
When not set, the value defaults to `127.0.0.1:5110`.
## License
Logging is made available with `env_logger`:
`export RUST_LOG=info`
Other logging levels include `debug`, `warn` and `error`.
### Setup
Clone this repo:
`git clone https://github.com/peachcloud/peach-network.git`
Move into the repo and compile:
`cd peach-network`
`cargo build --release`
Run the binary (sudo needed to satisfy permission requirements):
`sudo ./target/release/peach-network`
### Debian Packaging
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-network` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this.
Install `cargo-deb`:
`cargo install cargo-deb`
Move into the repo:
`cd peach-network`
Build the package:
`cargo deb`
The output will be written to `target/debian/peach-network_0.2.4_arm64.deb` (or similar).
Build the package (aarch64):
`cargo deb --target aarch64-unknown-linux-gnu`
Install the package as follows:
`sudo dpkg -i target/debian/peach-network_0.2.4_arm64.deb`
The service will be automatically enabled and started.
Uninstall the service:
`sudo apt-get remove peach-network`
Remove configuration files (not removed with `apt-get remove`):
`sudo apt-get purge peach-network`
### Example Usage
**Retrieve IP address for wlan0**
With microservice running, open a second terminal window and use `curl` to call server methods:
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ip", "params" : {"iface": "wlan0" }, "id":1 }' 127.0.0.1:5110`
Server responds with:
`{"jsonrpc":"2.0","result":"192.168.1.21","id":1}`
**Retrieve SSID of connected access point for wlan1**
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ssid", "params" : {"iface": "wlan1" }, "id":1 }' 127.0.0.1:5110`
Server response when interface is connected:
`{"jsonrpc":"2.0","result":"Home","id":1}`
Server response when interface is not connected:
`{"jsonrpc":"2.0","error":{"code":-32003,"message":"Failed to retrieve SSID for wlan1. Interface may not be connected."},"id":1}`
**Retrieve list of SSIDs for all networks in range of wlan0**
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "available_networks", "params" : {"iface": "wlan0" }, "id":1 }' 127.0.0.1:5110`
Server response when interface is connected:
`{"jsonrpc":"2.0","result":"[{\"frequency\":\"2412\",\"signal_level\":\"-72\",\"ssid\":\"Home\",\"flags\":\"[WPA2-PSK-CCMP][ESS]\"},{\"frequency\":\"2472\",\"signal_level\":\"-56\",\"ssid\":\"podetium\",\"flags\":\"[WPA2-PSK-CCMP+TKIP][ESS]\"}]","id":1}`
Server response when interface is not connected:
`{"jsonrpc":"2.0","error":{"code":-32006,"message":"No networks found in range of wlan0"},"id":1}`
**Retrieve network traffic statistics for wlan1**
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "traffic", "params" : {"iface": "wlan1" }, "id":1 }' 127.0.0.1:5110`
Server response if interface exists:
`{"jsonrpc":"2.0","result":"{\"received\":26396361,\"transmitted\":22352530}","id":1}`
Server response when interface is not found:
`{"jsonrpc":"2.0","error":{"code":-32004,"message":"Failed to retrieve network traffic for wlan3. Interface may not be connected"},"id":1}`
**Retrieve status information for wlan0**
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "status", "params" : {"iface": "wlan0" }, "id":1 }' 127.0.0.1:5110`
Server response if interface exists:
`{"jsonrpc":"2.0","result":"{\"address\":\"b8:27:eb:9b:5d:5f\",\"bssid\":\"f4:8c:eb:cd:31:81\",\"freq\":\"2412\",\"group_cipher\":\"CCMP\",\"id\":\"0\",\"ip_address\":\"192.168.0.162\",\"key_mgmt\":\"WPA2-PSK\",\"mode\":\"station\",\"pairwise_cipher\":\"CCMP\",\"ssid\":\"Home\",\"wpa_state\":\"COMPLETED\"}","id":1}`
Server response when interface is not found:
`{"jsonrpc":"2.0","error":{"code":-32013,"message":"Failed to open control interface for wpasupplicant: No such file or directory (os error 2)"},"id":1}`
### Licensing
AGPL-3.0
LGPL-3.0.

View File

@ -1,13 +0,0 @@
[Unit]
Description=Query and configure network interfaces using JSON-RPC over HTTP.
[Service]
Type=simple
User=root
Group=netdev
Environment="RUST_LOG=error"
ExecStart=/usr/bin/peach-network
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -1,351 +1,358 @@
use std::{error, io, str};
//! Custom error type for `peach-network`.
use jsonrpc_core::{types::error::Error, ErrorCode};
use std::io;
use std::num::ParseIntError;
use io::Error as IoError;
use probes::ProbeError;
use serde_json::error::Error as SerdeError;
use snafu::Snafu;
use regex::Error as RegexError;
use wpactrl::WpaError;
pub type BoxError = Box<dyn error::Error>;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
/// Custom error type encapsulating all possible errors when querying
/// network interfaces and modifying their state.
#[derive(Debug)]
pub enum NetworkError {
#[snafu(display("{}", err_msg))]
ActivateAp { err_msg: String },
#[snafu(display("{}", err_msg))]
ActivateClient { err_msg: String },
#[snafu(display("Failed to add network for {}", ssid))]
Add { ssid: String },
#[snafu(display("Failed to retrieve state for interface: {}", iface))]
NoState { iface: String, source: io::Error },
#[snafu(display("Failed to disable network {} for interface: {}", id, iface))]
Disable { id: String, iface: String },
#[snafu(display("Failed to disconnect {}", iface))]
Disconnect { iface: String },
#[snafu(display("Failed to generate wpa passphrase for {}: {}", ssid, source))]
GenWpaPassphrase { ssid: String, source: io::Error },
#[snafu(display("Failed to generate wpa passphrase for {}: {}", ssid, err_msg))]
GenWpaPassphraseWarning { ssid: String, err_msg: String },
#[snafu(display("No ID found for {} on interface: {}", ssid, iface))]
Id { ssid: String, iface: String },
#[snafu(display("Could not access IP address for interface: {}", iface))]
NoIp { iface: String, source: io::Error },
#[snafu(display("Could not find RSSI for interface: {}", iface))]
Rssi { iface: String },
#[snafu(display("Could not find signal quality (%) for interface: {}", iface))]
RssiPercent { iface: String },
#[snafu(display("Could not find SSID for interface: {}", iface))]
Ssid { iface: String },
#[snafu(display("No state found for interface: {}", iface))]
State { iface: String },
#[snafu(display("No status found for interface: {}", iface))]
Status { iface: String },
#[snafu(display("Could not find network traffic for interface: {}", iface))]
Traffic { iface: String },
#[snafu(display("No saved networks found for default interface"))]
/// Failed to add network.
Add {
/// SSID.
ssid: String,
},
/// Failed to retrieve network state.
NoState {
/// Interface.
iface: String,
/// Underlying error source.
source: IoError,
},
/// Failed to disable network.
Disable {
/// ID.
id: String,
/// Interface.
iface: String,
},
/// Failed to disconnect interface.
Disconnect {
/// Interface.
iface: String,
},
/// Failed to execute wpa_passphrase command.
GenWpaPassphrase {
/// SSID.
ssid: String,
/// Underlying error source.
source: IoError,
},
/// Failed to successfully generate wpa passphrase.
GenWpaPassphraseWarning {
/// SSID.
ssid: String,
/// Error message describing context.
err_msg: String,
},
/// Failed to retrieve ID for the given SSID and interface.
Id {
/// SSID.
ssid: String,
/// Interface.
iface: String,
},
/// Failed to retrieve IP address.
NoIp {
/// Inteface.
iface: String,
/// Underlying error source.
source: IoError,
},
/// Failed to retrieve RSSI.
Rssi {
/// Interface.
iface: String,
},
/// Failed to retrieve signal quality (%).
RssiPercent {
/// Interface.
iface: String,
},
/// Failed to retrieve SSID.
Ssid {
/// Interface.
iface: String,
},
/// Failed to retrieve state.
State {
/// Interface.
iface: String,
},
/// Failed to retrieve status.
Status {
/// Interface.
iface: String,
},
/// Failed to retieve network traffic.
Traffic {
/// Interface.
iface: String,
},
/// No saved network found for the default interface.
SavedNetworks,
#[snafu(display("No networks found in range of interface: {}", iface))]
AvailableNetworks { iface: String },
#[snafu(display("Missing expected parameters: {}", e))]
MissingParams { e: Error },
#[snafu(display("Failed to set new password for network {} on {}", id, iface))]
Modify { id: String, iface: String },
#[snafu(display("No IP found for interface: {}", iface))]
Ip { iface: String },
#[snafu(display("Failed to parse integer from string for RSSI value: {}", source))]
ParseString { source: std::num::ParseIntError },
#[snafu(display(
"Failed to retrieve network traffic measurement for {}: {}",
iface,
source
))]
NoTraffic { iface: String, source: ProbeError },
#[snafu(display("Failed to reassociate with WiFi network for interface: {}", iface))]
Reassociate { iface: String },
#[snafu(display("Failed to force reread of wpa_supplicant configuration file"))]
/// No networks found in range.
AvailableNetworks {
/// Interface.
iface: String,
},
/// Failed to set new password.
Modify {
/// ID.
id: String,
/// Interface.
iface: String,
},
/// Failed to retrieve IP address.
Ip {
/// Interface.
iface: String,
},
/// Failed to parse integer from string.
ParseInt(ParseIntError),
/// Failed to retrieve network traffic measurement.
NoTraffic {
/// Interface.
iface: String,
/// Underlying error source.
source: ProbeError,
},
/// Failed to reassociate with WiFi network.
Reassociate {
/// Interface.
iface: String,
},
/// Failed to force reread of wpa_supplicant configuration file.
Reconfigure,
#[snafu(display("Failed to reconnect with WiFi network for interface: {}", iface))]
Reconnect { iface: String },
#[snafu(display("Regex command failed"))]
Regex { source: regex::Error },
#[snafu(display("Failed to delete network {} for interface: {}", id, iface))]
Delete { id: String, iface: String },
#[snafu(display("Failed to retrieve state of wlan0 service: {}", source))]
WlanState { source: io::Error },
#[snafu(display("Failed to retrieve connection state of wlan0 interface: {}", source))]
WlanOperstate { source: io::Error },
#[snafu(display("Failed to save configuration changes to file"))]
/// Failed to reconnect with WiFi network.
Reconnect {
/// Interface.
iface: String,
},
/// Failed to execute Regex command.
Regex(RegexError),
/// Failed to delete network.
Delete {
/// ID.
id: String,
/// Interface.
iface: String,
},
/// Failed to retrieve state of wlan0 service.
WlanState(IoError),
/// Failed to retrieve connection state of wlan0 interface.
WlanOperstate(IoError),
/// Failed to save wpa_supplicant configuration changes to file.
Save,
#[snafu(display("Failed to connect to network {} for interface: {}", id, iface))]
Connect { id: String, iface: String },
#[snafu(display("Failed to start ap0 service: {}", source))]
StartAp0 { source: io::Error },
#[snafu(display("Failed to start wlan0 service: {}", source))]
StartWlan0 { source: io::Error },
#[snafu(display("JSON serialization failed: {}", source))]
SerdeSerialize { source: SerdeError },
#[snafu(display("Failed to open control interface for wpasupplicant"))]
WpaCtrlOpen {
#[snafu(source(from(failure::Error, std::convert::Into::into)))]
source: BoxError,
},
#[snafu(display("Request to wpasupplicant via wpactrl failed"))]
WpaCtrlRequest {
#[snafu(source(from(failure::Error, std::convert::Into::into)))]
source: BoxError,
/// Failed to connect to network.
Connect {
/// ID.
id: String,
/// Interface.
iface: String,
},
/// Failed to start ap0 service.
StartAp0(IoError),
/// Failed to start wlan0 service.
StartWlan0(IoError),
/// Failed to execute wpa-ctrl command.
WpaCtrl(WpaError),
}
impl From<NetworkError> for Error {
fn from(err: NetworkError) -> Self {
match &err {
NetworkError::ActivateAp { err_msg } => Error {
code: ErrorCode::ServerError(-32015),
message: err_msg.to_string(),
data: None,
},
NetworkError::ActivateClient { err_msg } => Error {
code: ErrorCode::ServerError(-32017),
message: err_msg.to_string(),
data: None,
},
NetworkError::Add { ssid } => Error {
code: ErrorCode::ServerError(-32000),
message: format!("Failed to add network for {}", ssid),
data: None,
},
NetworkError::NoState { iface, source } => Error {
code: ErrorCode::ServerError(-32022),
message: format!(
"Failed to retrieve interface state for {}: {}",
iface, source
),
data: None,
},
NetworkError::Disable { id, iface } => Error {
code: ErrorCode::ServerError(-32029),
message: format!("Failed to disable network {} for {}", id, iface),
data: None,
},
NetworkError::Disconnect { iface } => Error {
code: ErrorCode::ServerError(-32032),
message: format!("Failed to disconnect {}", iface),
data: None,
},
NetworkError::GenWpaPassphrase { ssid, source } => Error {
code: ErrorCode::ServerError(-32025),
message: format!("Failed to generate wpa passphrase for {}: {}", ssid, source),
data: None,
},
NetworkError::GenWpaPassphraseWarning { ssid, err_msg } => Error {
code: ErrorCode::ServerError(-32036),
message: format!(
"Failed to generate wpa passphrase for {}: {}",
ssid, err_msg
),
data: None,
},
NetworkError::Id { iface, ssid } => Error {
code: ErrorCode::ServerError(-32026),
message: format!("No ID found for {} on interface {}", ssid, iface),
data: None,
},
NetworkError::NoIp { iface, source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve IP address for {}: {}", iface, source),
data: None,
},
NetworkError::Rssi { iface } => Error {
code: ErrorCode::ServerError(-32002),
message: format!(
"Failed to retrieve RSSI for {}. Interface may not be connected",
iface
),
data: None,
},
NetworkError::RssiPercent { iface } => Error {
code: ErrorCode::ServerError(-32034),
message: format!(
"Failed to retrieve signal quality (%) for {}. Interface may not be connected",
iface
),
data: None,
},
NetworkError::Ssid { iface } => Error {
code: ErrorCode::ServerError(-32003),
message: format!(
"Failed to retrieve SSID for {}. Interface may not be connected",
iface
),
data: None,
},
NetworkError::State { iface } => Error {
code: ErrorCode::ServerError(-32023),
message: format!("No state found for {}. Interface may not exist", iface),
data: None,
},
NetworkError::Status { iface } => Error {
code: ErrorCode::ServerError(-32024),
message: format!("No status found for {}. Interface may not exist", iface),
data: None,
},
NetworkError::Traffic { iface } => Error {
code: ErrorCode::ServerError(-32004),
message: format!(
"No network traffic statistics found for {}. Interface may not exist",
iface
),
data: None,
},
NetworkError::SavedNetworks => Error {
code: ErrorCode::ServerError(-32005),
message: "No saved networks found".to_string(),
data: None,
},
NetworkError::AvailableNetworks { iface } => Error {
code: ErrorCode::ServerError(-32006),
message: format!("No networks found in range of {}", iface),
data: None,
},
NetworkError::MissingParams { e } => e.clone(),
NetworkError::Modify { id, iface } => Error {
code: ErrorCode::ServerError(-32033),
message: format!("Failed to set new password for network {} on {}", id, iface),
data: None,
},
NetworkError::Ip { iface } => Error {
code: ErrorCode::ServerError(-32007),
message: format!("No IP address found for {}", iface),
data: None,
},
NetworkError::ParseString { source } => Error {
code: ErrorCode::ServerError(-32035),
message: format!(
"Failed to parse integer from string for RSSI value: {}",
source
),
data: None,
},
NetworkError::NoTraffic { iface, source } => Error {
code: ErrorCode::ServerError(-32015),
message: format!(
"Failed to retrieve network traffic statistics for {}: {}",
iface, source
),
data: None,
},
NetworkError::Reassociate { iface } => Error {
code: ErrorCode::ServerError(-32008),
message: format!("Failed to reassociate with WiFi network for {}", iface),
data: None,
},
NetworkError::Reconfigure => Error {
code: ErrorCode::ServerError(-32030),
message: "Failed to force reread of wpa_supplicant configuration file".to_string(),
data: None,
},
NetworkError::Reconnect { iface } => Error {
code: ErrorCode::ServerError(-32009),
message: format!("Failed to reconnect with WiFi network for {}", iface),
data: None,
},
NetworkError::Regex { source } => Error {
code: ErrorCode::ServerError(-32010),
message: format!("Regex command error: {}", source),
data: None,
},
NetworkError::Delete { id, iface } => Error {
code: ErrorCode::ServerError(-32028),
message: format!("Failed to delete network {} for {}", id, iface),
data: None,
},
NetworkError::WlanState { source } => Error {
code: ErrorCode::ServerError(-32011),
message: format!("Failed to retrieve state of wlan0 service: {}", source),
data: None,
},
NetworkError::WlanOperstate { source } => Error {
code: ErrorCode::ServerError(-32021),
message: format!(
"Failed to retrieve connection state of wlan0 interface: {}",
source
),
data: None,
},
NetworkError::Save => Error {
code: ErrorCode::ServerError(-32031),
message: "Failed to save configuration changes to file".to_string(),
data: None,
},
NetworkError::Connect { id, iface } => Error {
code: ErrorCode::ServerError(-32027),
message: format!("Failed to connect to network {} for {}", id, iface),
data: None,
},
NetworkError::StartAp0 { source } => Error {
code: ErrorCode::ServerError(-32016),
message: format!("Failed to start ap0 service: {}", source),
data: None,
},
NetworkError::StartWlan0 { source } => Error {
code: ErrorCode::ServerError(-32018),
message: format!("Failed to start wlan0 service: {}", source),
data: None,
},
NetworkError::SerdeSerialize { source } => Error {
code: ErrorCode::ServerError(-32012),
message: format!("JSON serialization failed: {}", source),
data: None,
},
NetworkError::WpaCtrlOpen { source } => Error {
code: ErrorCode::ServerError(-32013),
message: format!(
"Failed to open control interface for wpasupplicant: {}",
source
),
data: None,
},
NetworkError::WpaCtrlRequest { source } => Error {
code: ErrorCode::ServerError(-32014),
message: format!("WPA supplicant request failed: {}", source),
data: None,
},
impl std::error::Error for NetworkError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
NetworkError::Add { .. } => None,
NetworkError::NoState { ref source, .. } => Some(source),
NetworkError::Disable { .. } => None,
NetworkError::Disconnect { .. } => None,
NetworkError::GenWpaPassphrase { ref source, .. } => Some(source),
NetworkError::GenWpaPassphraseWarning { .. } => None,
NetworkError::Id { .. } => None,
NetworkError::NoIp { ref source, .. } => Some(source),
NetworkError::Rssi { .. } => None,
NetworkError::RssiPercent { .. } => None,
NetworkError::Ssid { .. } => None,
NetworkError::State { .. } => None,
NetworkError::Status { .. } => None,
NetworkError::Traffic { .. } => None,
NetworkError::SavedNetworks => None,
NetworkError::AvailableNetworks { .. } => None,
NetworkError::Modify { .. } => None,
NetworkError::Ip { .. } => None,
NetworkError::ParseInt(ref source) => Some(source),
NetworkError::NoTraffic { ref source, .. } => Some(source),
NetworkError::Reassociate { .. } => None,
NetworkError::Reconfigure { .. } => None,
NetworkError::Reconnect { .. } => None,
NetworkError::Regex(ref source) => Some(source),
NetworkError::Delete { .. } => None,
NetworkError::WlanState(ref source) => Some(source),
NetworkError::WlanOperstate(ref source) => Some(source),
NetworkError::Save => None,
NetworkError::Connect { .. } => None,
NetworkError::StartWlan0(ref source) => Some(source),
NetworkError::StartAp0(ref source) => Some(source),
NetworkError::WpaCtrl(ref source) => Some(source),
}
}
}
impl std::fmt::Display for NetworkError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
NetworkError::Add { ref ssid } => {
write!(f, "Failed to add network for {}", ssid)
}
NetworkError::NoState { ref iface, .. } => {
write!(f, "Failed to retrieve state for interface: {}", iface)
}
NetworkError::Disable { ref id, ref iface } => {
write!(
f,
"Failed to disable network {} for interface: {}",
id, iface
)
}
NetworkError::Disconnect { ref iface } => {
write!(f, "Failed to disconnect {}", iface)
}
NetworkError::GenWpaPassphrase { ref ssid, .. } => {
write!(f, "Failed to generate wpa passphrase for {}", ssid)
}
NetworkError::GenWpaPassphraseWarning {
ref ssid,
ref err_msg,
} => {
write!(
f,
"Failed to generate wpa passphrase for {}: {}",
ssid, err_msg
)
}
NetworkError::Id {
ref ssid,
ref iface,
} => {
write!(f, "No ID found for {} on interface: {}", ssid, iface)
}
NetworkError::NoIp { ref iface, .. } => {
write!(f, "Could not access IP address for interface: {}", iface)
}
NetworkError::Rssi { ref iface } => {
write!(f, "Could not find RSSI for interface: {}", iface)
}
NetworkError::RssiPercent { ref iface } => {
write!(
f,
"Could not find signal quality (%) for interface: {}",
iface
)
}
NetworkError::Ssid { ref iface } => {
write!(f, "Could not find SSID for interface: {}", iface)
}
NetworkError::State { ref iface } => {
write!(f, "No state found for interface: {}", iface)
}
NetworkError::Status { ref iface } => {
write!(f, "No status found for interface: {}", iface)
}
NetworkError::Traffic { ref iface } => {
write!(f, "Could not find network traffic for interface: {}", iface)
}
NetworkError::SavedNetworks => {
write!(f, "No saved networks found for default interface")
}
NetworkError::AvailableNetworks { ref iface } => {
write!(f, "No networks found in range of interface: {}", iface)
}
NetworkError::Modify { ref id, ref iface } => {
write!(
f,
"Failed to set new password for network {} on {}",
id, iface
)
}
NetworkError::Ip { ref iface } => {
write!(f, "No IP found for interface: {}", iface)
}
NetworkError::ParseInt(_) => {
write!(f, "Failed to parse integer from string for RSSI value")
}
NetworkError::NoTraffic { ref iface, .. } => {
write!(
f,
"Failed to retrieve network traffic measurement for {}",
iface
)
}
NetworkError::Reassociate { ref iface } => {
write!(
f,
"Failed to reassociate with WiFi network for interface: {}",
iface
)
}
NetworkError::Reconfigure => {
write!(
f,
"Failed to force reread of wpa_supplicant configuration file"
)
}
NetworkError::Reconnect { ref iface } => {
write!(
f,
"Failed to reconnect with WiFi network for interface: {}",
iface
)
}
NetworkError::Regex(_) => write!(f, "Regex command failed"),
NetworkError::Delete { ref id, ref iface } => {
write!(
f,
"Failed to delete network {} for interface: {}",
id, iface
)
}
NetworkError::WlanState(_) => write!(f, "Failed to retrieve state of wlan0 service"),
NetworkError::WlanOperstate(_) => {
write!(f, "Failed to retrieve connection state of wlan0 interface")
}
NetworkError::Save => write!(f, "Failed to save configuration changes to file"),
NetworkError::Connect { ref id, ref iface } => {
write!(
f,
"Failed to connect to network {} for interface: {}",
id, iface
)
}
NetworkError::StartWlan0(_) => write!(f, "Failed to start ap0 service"),
NetworkError::StartAp0(_) => write!(f, "Failed to start wlan0 service"),
NetworkError::WpaCtrl(_) => write!(f, "WpaCtrl command failed"),
}
}
}
impl From<WpaError> for NetworkError {
fn from(err: WpaError) -> Self {
NetworkError::WpaCtrl(err)
}
}
impl From<ParseIntError> for NetworkError {
fn from(err: ParseIntError) -> Self {
NetworkError::ParseInt(err)
}
}
impl From<RegexError> for NetworkError {
fn from(err: RegexError) -> Self {
NetworkError::Regex(err)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
use std::process;
use log::error;
fn main() {
// initalize the logger
env_logger::init();
// handle errors returned from `run`
if let Err(e) = peach_network::run() {
error!("Application error: {}", e);
process::exit(1);
}
}

View File

@ -1,6 +1,6 @@
//! Retrieve network data and modify interface state.
//!
//! This module contains the core logic of the `peach-network` microservice and
//! This module contains the core logic of the `peach-network` and
//! provides convenience wrappers for a range of `wpasupplicant` commands,
//! many of which are ordinarily executed using `wpa_cli` (a WPA command line
//! client).
@ -12,7 +12,7 @@
//! system calls to systemd (via `systemctl`). Further networking functionality
//! is provided by making system calls to retrieve interface state and write
//! access point credentials to `wpa_supplicant-wlan0.conf`.
//!
use std::{
fs::OpenOptions,
io::prelude::*,
@ -21,72 +21,67 @@ use std::{
str,
};
use crate::error::{
GenWpaPassphrase, NetworkError, NoIp, NoState, NoTraffic, ParseString, SerdeSerialize,
StartAp0, StartWlan0, WlanState, WpaCtrlOpen, WpaCtrlRequest,
};
use probes::network;
use serde::{Deserialize, Serialize};
use snafu::ResultExt;
#[cfg(feature = "miniserde_support")]
use miniserde::{Deserialize, Serialize};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
use crate::error::NetworkError;
use crate::utils;
/// Network interface name.
#[derive(Debug, Deserialize)]
pub struct Iface {
pub iface: String,
}
/// Network interface name and network identifier.
#[derive(Debug, Deserialize)]
pub struct IfaceId {
pub iface: String,
pub id: String,
}
/// Network interface name, network identifier and password.
#[derive(Debug, Deserialize)]
pub struct IfaceIdPass {
pub iface: String,
pub id: String,
pub pass: String,
}
/// Network interface name and network SSID.
#[derive(Debug, Deserialize)]
pub struct IfaceSsid {
pub iface: String,
pub ssid: String,
}
/// Network SSID.
#[derive(Debug, Serialize)]
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Network {
/// Service Set Identifier (SSID).
pub ssid: String,
}
/// Access point data retrieved via scan.
#[derive(Debug, Serialize)]
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Scan {
/// Frequency.
pub frequency: String,
/// Protocol.
pub protocol: String,
/// Signal strength.
pub signal_level: String,
/// SSID.
pub ssid: String,
}
/// Status data for a network interface.
#[derive(Debug, Serialize)]
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Status {
/// MAC address.
pub address: Option<String>,
/// Basic Service Set Identifier (BSSID).
pub bssid: Option<String>,
/// Frequency.
pub freq: Option<String>,
/// Group cipher.
pub group_cipher: Option<String>,
/// Local ID.
pub id: Option<String>,
/// IP address.
pub ip_address: Option<String>,
/// Key management.
pub key_mgmt: Option<String>,
/// Mode.
pub mode: Option<String>,
/// Pairwise cipher.
pub pairwise_cipher: Option<String>,
/// SSID.
pub ssid: Option<String>,
/// WPA state.
pub wpa_state: Option<String>,
}
@ -109,16 +104,24 @@ impl Status {
}
/// Received and transmitted network traffic (bytes).
#[derive(Debug, Serialize)]
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Traffic {
/// Total bytes received.
pub received: u64,
/// Total bytes transmitted.
pub transmitted: u64,
}
/// SSID and password for a wireless access point.
#[derive(Debug, Deserialize)]
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct WiFi {
/// SSID.
pub ssid: String,
/// Password.
pub pass: String,
}
@ -132,22 +135,15 @@ pub struct WiFi {
/// * `iface` - A string slice holding the name of a wireless network interface
///
/// If the scan results include one or more access points for the given network
/// interface, an `Ok` `Result` type is returned containing `Some(String)` -
/// where `String` is a serialized vector of `Scan` structs containing
/// data for the in-range access points. If no access points are found,
/// a `None` type is returned in the `Result`. In the event of an error, a
/// `NetworkError` is returned in the `Result`. The `NetworkError` is then
/// enumerated to a specific error type and an appropriate JSON RPC response is
/// sent to the caller.
///
pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
/// interface, an `Ok` `Result` type is returned containing `Some(Vec<Scan>)`.
/// The vector of `Scan` structs contains data for the in-range access points.
/// If no access points are found, a `None` type is returned in the `Result`.
/// In the event of an error, a `NetworkError` is returned in the `Result`.
pub fn available_networks(iface: &str) -> Result<Option<Vec<Scan>>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
wpa.request("SCAN").context(WpaCtrlRequest)?;
let networks = wpa.request("SCAN_RESULTS").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
wpa.request("SCAN")?;
let networks = wpa.request("SCAN_RESULTS")?;
let mut scan = Vec::new();
for network in networks.lines() {
let v: Vec<&str> = network.split('\t').collect();
@ -178,8 +174,7 @@ pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
if scan.is_empty() {
Ok(None)
} else {
let results = serde_json::to_string(&scan).context(SerdeSerialize)?;
Ok(Some(results))
Ok(Some(scan))
}
}
@ -195,17 +190,11 @@ pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
/// found in the list of saved networks, an `Ok` `Result` type is returned
/// containing `Some(String)` - where `String` is the network identifier.
/// If no match is found, a `None` type is returned in the `Result`. In the
/// event of an error, a `NetworkError` is returned in the `Result`. The
/// `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let networks = wpa.request("LIST_NETWORKS").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let networks = wpa.request("LIST_NETWORKS")?;
let mut id = Vec::new();
for network in networks.lines() {
let v: Vec<&str> = network.split('\t').collect();
@ -233,13 +222,13 @@ pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
/// an `Ok` `Result` type is returned containing `Some(String)` - where `String`
/// is the IP address of the interface. If no match is found, a `None` type is
/// returned in the `Result`. In the event of an error, a `NetworkError` is
/// returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// returned in the `Result`.
pub fn ip(iface: &str) -> Result<Option<String>, NetworkError> {
let net_if: String = iface.to_string();
let ifaces = get_if_addrs::get_if_addrs().context(NoIp { iface: net_if })?;
let ifaces = get_if_addrs::get_if_addrs().map_err(|source| NetworkError::NoIp {
iface: net_if,
source,
})?;
let ip = ifaces
.iter()
.find(|&i| i.name == iface)
@ -260,16 +249,11 @@ pub fn ip(iface: &str) -> Result<Option<String>, NetworkError> {
/// is the RSSI (Received Signal Strength Indicator) of the connection measured
/// in dBm. If signal strength is not found, a `None` type is returned in the
/// `Result`. In the event of an error, a `NetworkError` is returned in the
/// `Result`. The `NetworkError` is then enumerated to a specific error type and
/// an appropriate JSON RPC response is sent to the caller.
///
/// `Result`.
pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let status = wpa.request("SIGNAL_POLL").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let status = wpa.request("SIGNAL_POLL")?;
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
if rssi.is_none() {
@ -292,22 +276,17 @@ pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
/// is the RSSI (Received Signal Strength Indicator) of the connection measured
/// as a percentage. If signal strength is not found, a `None` type is returned
/// in the `Result`. In the event of an error, a `NetworkError` is returned in
/// the `Result`. The `NetworkError` is then enumerated to a specific error type
/// and an appropriate JSON RPC response is sent to the caller.
///
/// the `Result`.
pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let status = wpa.request("SIGNAL_POLL").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let status = wpa.request("SIGNAL_POLL")?;
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
match rssi {
Some(rssi) => {
// parse the string to a signed integer (for math)
let rssi_parsed = rssi.parse::<i32>().context(ParseString)?;
let rssi_parsed = rssi.parse::<i32>()?;
// perform rssi (dBm) to quality (%) conversion
let quality_percent = 2 * (rssi_parsed + 100);
// convert signal quality integer to string
@ -327,16 +306,13 @@ pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
///
/// If the wpasupplicant configuration file contains credentials for one or
/// more access points, an `Ok` `Result` type is returned containing
/// `Some(String)` - where `String` is a serialized vector of `Network` structs
/// containing the SSIDs of all saved networks. If no network credentials are
/// found, a `None` type is returned in the `Result`. In the event of an error,
/// a `NetworkError` is returned in the `Result`. The `NetworkError` is then
/// enumerated to a specific error type and an appropriate JSON RPC response is
/// sent to the caller.
///
pub fn saved_networks() -> Result<Option<String>, NetworkError> {
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
let networks = wpa.request("LIST_NETWORKS").context(WpaCtrlRequest)?;
/// `Some(Vec<Network>)`. The vector of `Network` structs contains the SSIDs
/// of all saved networks. If no network credentials are found, a `None` type
/// is returned in the `Result`. In the event of an error, a `NetworkError` is
/// returned in the `Result`.
pub fn saved_networks() -> Result<Option<Vec<Network>>, NetworkError> {
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
let networks = wpa.request("LIST_NETWORKS")?;
let mut ssids = Vec::new();
for network in networks.lines() {
let v: Vec<&str> = network.split('\t').collect();
@ -351,8 +327,7 @@ pub fn saved_networks() -> Result<Option<String>, NetworkError> {
if ssids.is_empty() {
Ok(None)
} else {
let results = serde_json::to_string(&ssids).context(SerdeSerialize)?;
Ok(Some(results))
Ok(Some(ssids))
}
}
@ -366,17 +341,11 @@ pub fn saved_networks() -> Result<Option<String>, NetworkError> {
/// an `Ok` `Result` type is returned containing `Some(String)` - where `String`
/// is the SSID of the associated network. If SSID is not found, a `None` type
/// is returned in the `Result`. In the event of an error, a `NetworkError` is
/// returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// returned in the `Result`.
pub fn ssid(iface: &str) -> Result<Option<String>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let status = wpa.request("STATUS").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let status = wpa.request("STATUS")?;
// pass the regex pattern and status output to the regex finder
let ssid = utils::regex_finder(r"\nssid=(.*)\n", &status)?;
@ -394,9 +363,7 @@ pub fn ssid(iface: &str) -> Result<Option<String>, NetworkError> {
/// returned containing `Some(String)` - where `String` is the state of the
/// network interface. If state is not found, a `None` type is returned in the
/// `Result`. In the event of an error, a `NetworkError` is returned in the
/// `Result`. The `NetworkError` is then enumerated to a specific error type and
/// an appropriate JSON RPC response is sent to the caller.
///
/// `Result`.
pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
// construct the interface operstate path
let iface_path: String = format!("/sys/class/net/{}/operstate", iface);
@ -404,7 +371,10 @@ pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
let output = Command::new("cat")
.arg(iface_path)
.output()
.context(NoState { iface })?;
.map_err(|source| NetworkError::NoState {
iface: iface.to_string(),
source,
})?;
if !output.stdout.is_empty() {
// unwrap the command result and convert to String
let mut state = String::from_utf8(output.stdout).unwrap();
@ -427,17 +397,11 @@ pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
/// returned containing `Some(Status)` - where `Status` is a `struct`
/// containing the aggregated interface data in named fields. If status is not
/// found, a `None` type is returned in the `Result`. In the event of an error,
/// a `NetworkError` is returned in the `Result`. The `NetworkError` is then
/// enumerated to a specific error type and an appropriate JSON RPC response is
/// sent to the caller.
///
/// a `NetworkError` is returned in the `Result`.
pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let wpa_status = wpa.request("STATUS").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let wpa_status = wpa.request("STATUS")?;
// pass the regex pattern and status output to the regex finder
let state = utils::regex_finder(r"wpa_state=(.*)\n", &wpa_status)?;
@ -486,16 +450,16 @@ pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
/// * `iface` - A string slice holding the name of a wireless network interface
///
/// If the network traffic statistics are found for the given interface, an `Ok`
/// `Result` type is returned containing `Some(String)` - where `String` is a
/// serialized `Traffic` `struct` with fields for received and transmitted
/// network data statistics. If network traffic statistics are not found for the
/// given interface, a `None` type is returned in the `Result`. In the event of
/// an error, a `NetworkError` is returned in the `Result`. The `NetworkError`
/// is then enumerated to a specific error type and an appropriate JSON RPC
/// response is sent to the caller.
///
pub fn traffic(iface: &str) -> Result<Option<String>, NetworkError> {
let network = network::read().context(NoTraffic { iface })?;
/// `Result` type is returned containing `Some(Traffic)`. The `Traffic` `struct`
/// includes fields for received and transmitted network data statistics. If
/// network traffic statistics are not found for the given interface, a `None`
/// type is returned in the `Result`. In the event of an error, a `NetworkError`
/// is returned in the `Result`.
pub fn traffic(iface: &str) -> Result<Option<Traffic>, NetworkError> {
let network = network::read().map_err(|source| NetworkError::NoTraffic {
iface: iface.to_string(),
source,
})?;
// iterate through interfaces returned in network data
for (interface, traffic) in network.interfaces {
if interface == iface {
@ -505,9 +469,7 @@ pub fn traffic(iface: &str) -> Result<Option<String>, NetworkError> {
received,
transmitted,
};
// TODO: add test for SerdeSerialize error
let t = serde_json::to_string(&traffic).context(SerdeSerialize)?;
return Ok(Some(t));
return Ok(Some(traffic));
}
}
@ -521,9 +483,6 @@ pub fn traffic(iface: &str) -> Result<Option<String>, NetworkError> {
/// A `systemctl `command is invoked which starts the `ap0` interface service.
/// If the command executes successfully, an `Ok` `Result` type is returned.
/// In the event of an error, a `NetworkError` is returned in the `Result`.
/// The `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
pub fn activate_ap() -> Result<(), NetworkError> {
// start the ap0 interface service
Command::new("sudo")
@ -531,7 +490,7 @@ pub fn activate_ap() -> Result<(), NetworkError> {
.arg("start")
.arg("wpa_supplicant@ap0.service")
.output()
.context(StartAp0)?;
.map_err(NetworkError::StartAp0)?;
Ok(())
}
@ -541,9 +500,6 @@ pub fn activate_ap() -> Result<(), NetworkError> {
/// A `systemctl` command is invoked which starts the `wlan0` interface service.
/// If the command executes successfully, an `Ok` `Result` type is returned.
/// In the event of an error, a `NetworkError` is returned in the `Result`.
/// The `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
pub fn activate_client() -> Result<(), NetworkError> {
// start the wlan0 interface service
Command::new("sudo")
@ -551,7 +507,7 @@ pub fn activate_client() -> Result<(), NetworkError> {
.arg("start")
.arg("wpa_supplicant@wlan0.service")
.output()
.context(StartWlan0)?;
.map_err(NetworkError::StartWlan0)?;
Ok(())
}
@ -565,10 +521,7 @@ pub fn activate_client() -> Result<(), NetworkError> {
/// If configuration parameters are successfully generated from the provided
/// SSID and password and appended to `wpa_supplicant-wlan0.conf`, an `Ok`
/// `Result` type is returned. In the event of an error, a `NetworkError` is
/// returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// returned in the `Result`.
pub fn add(wifi: &WiFi) -> Result<(), NetworkError> {
// generate configuration based on provided ssid & password
let output = Command::new("wpa_passphrase")
@ -576,7 +529,10 @@ pub fn add(wifi: &WiFi) -> Result<(), NetworkError> {
.arg(&wifi.pass)
.stdout(Stdio::piped())
.output()
.context(GenWpaPassphrase { ssid: &wifi.ssid })?;
.map_err(|source| NetworkError::GenWpaPassphrase {
ssid: wifi.ssid.to_string(),
source,
})?;
// prepend newline to wpa_details to safeguard against malformed supplicant
let mut wpa_details = "\n".as_bytes().to_vec();
@ -614,14 +570,13 @@ pub fn add(wifi: &WiFi) -> Result<(), NetworkError> {
/// are checked. If the service is active but the interface is down (ie. not
/// currently connected to an access point), then the access point is activated
/// by calling the `activate_ap()` function.
///
pub fn check_iface() -> Result<(), NetworkError> {
// returns 0 if the service is currently active
let wlan0_status = Command::new("/usr/bin/systemctl")
.arg("is-active")
.arg("wpa_supplicant@wlan0.service")
.status()
.context(WlanState)?;
.map_err(NetworkError::WlanState)?;
// returns the current state of the wlan0 interface
let iface_state = state("wlan0")?;
@ -651,18 +606,12 @@ pub fn check_iface() -> Result<(), NetworkError> {
/// If the network connection is successfully activated for the access point
/// represented by the given network identifier on the given wireless interface,
/// an `Ok` `Result`type is returned. In the event of an error, a `NetworkError`
/// is returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// is returned in the `Result`.
pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let select = format!("SELECT {}", id);
wpa.request(&select).context(WpaCtrlRequest)?;
wpa.request(&select)?;
Ok(())
}
@ -676,18 +625,12 @@ pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
/// If the network configuration parameters are successfully deleted for
/// the access point represented by the given network identifier, an `Ok`
/// `Result`type is returned. In the event of an error, a `NetworkError` is
/// returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// returned in the `Result`.
pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let remove = format!("REMOVE_NETWORK {}", id);
wpa.request(&remove).context(WpaCtrlRequest)?;
wpa.request(&remove)?;
Ok(())
}
@ -701,17 +644,12 @@ pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
/// If the network connection is successfully disabled for the access point
/// represented by the given network identifier, an `Ok` `Result`type is
/// returned. In the event of an error, a `NetworkError` is returned in the
/// `Result`. The `NetworkError` is then enumerated to a specific error type and
/// an appropriate JSON RPC response is sent to the caller.
///
/// `Result`.
pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let disable = format!("DISABLE_NETWORK {}", id);
wpa.request(&disable).context(WpaCtrlRequest)?;
wpa.request(&disable)?;
Ok(())
}
@ -723,18 +661,12 @@ pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
///
/// If the network connection is successfully disconnected for the given
/// wireless interface, an `Ok` `Result` type is returned. In the event of an
/// error, a `NetworkError` is returned in the `Result`. The `NetworkError` is
/// then enumerated to a specific error type and an appropriate JSON RPC
/// response is sent to the caller.
///
/// error, a `NetworkError` is returned in the `Result`.
pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let disconnect = "DISCONNECT".to_string();
wpa.request(&disconnect).context(WpaCtrlRequest)?;
wpa.request(&disconnect)?;
Ok(())
}
@ -748,18 +680,12 @@ pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
///
/// If the password is successfully updated for the access point represented by
/// the given network identifier, an `Ok` `Result` type is returned. In the
/// event of an error, a `NetworkError` is returned in the `Result`. The
/// `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
let new_pass = format!("NEW_PASSWORD {} {}", id, pass);
wpa.request(&new_pass).context(WpaCtrlRequest)?;
wpa.request(&new_pass)?;
Ok(())
}
@ -771,17 +697,11 @@ pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
///
/// If the network connection is successfully reassociated for the given
/// wireless interface, an `Ok` `Result` type is returned. In the event of an
/// error, a `NetworkError` is returned in the `Result`. The `NetworkError` is
/// then enumerated to a specific error type and an appropriate JSON RPC
/// response is sent to the caller.
///
/// error, a `NetworkError` is returned in the `Result`.
pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
wpa.request("REASSOCIATE").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
wpa.request("REASSOCIATE")?;
Ok(())
}
@ -790,13 +710,10 @@ pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
/// If the reconfigure command is successfully executed, indicating a reread
/// of the `wpa_supplicant.conf` file by the `wpa_supplicant` process, an `Ok`
/// `Result` type is returned. In the event of an error, a `NetworkError` is
/// returned in the `Result`. The `NetworkError` is then enumerated to a
/// specific error type and an appropriate JSON RPC response is sent to the
/// caller.
///
/// returned in the `Result`.
pub fn reconfigure() -> Result<(), NetworkError> {
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
wpa.request("RECONFIGURE").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
wpa.request("RECONFIGURE")?;
Ok(())
}
@ -808,18 +725,12 @@ pub fn reconfigure() -> Result<(), NetworkError> {
///
/// If the network connection is successfully disconnected and reconnected for
/// the given wireless interface, an `Ok` `Result` type is returned. In the
/// event of an error, a `NetworkError` is returned in the `Result`. The
/// `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
let mut wpa = wpactrl::WpaCtrl::new()
.ctrl_path(wpa_path)
.open()
.context(WpaCtrlOpen)?;
wpa.request("DISCONNECT").context(WpaCtrlRequest)?;
wpa.request("RECONNECT").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
wpa.request("DISCONNECT")?;
wpa.request("RECONNECT")?;
Ok(())
}
@ -827,12 +738,9 @@ pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
///
/// If wireless network configuration updates are successfully save to the
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
/// event of an error, a `NetworkError` is returned in the `Result`. The
/// `NetworkError` is then enumerated to a specific error type and an
/// appropriate JSON RPC response is sent to the caller.
///
/// event of an error, a `NetworkError` is returned in the `Result`.
pub fn save() -> Result<(), NetworkError> {
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
wpa.request("SAVE_CONFIG").context(WpaCtrlRequest)?;
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
wpa.request("SAVE_CONFIG")?;
Ok(())
}

View File

@ -1,7 +1,6 @@
use regex::Regex;
use snafu::ResultExt;
use crate::error::*;
use crate::error::NetworkError;
/// Return matches for a given Regex pattern and text
///
@ -11,7 +10,7 @@ use crate::error::*;
/// * `text` - A string slice containing the text to be matched on
///
pub fn regex_finder(pattern: &str, text: &str) -> Result<Option<String>, NetworkError> {
let re = Regex::new(pattern).context(Regex)?;
let re = Regex::new(pattern)?;
let caps = re.captures(text);
let result = caps.map(|caps| caps[1].to_string());