# Programming your networking tools with Rust
NetworkManager is widely known as the main network management suite for Linux distributions. People manage their specific use casTaes usually with bash scripts calling nmcli binary to perform operations and checking on the stderr or error code to understand if everything was configured as expected. This might be enough for simple setups but not for complex ones.
Long bash scripts are hard to debug and maintain and that without mentioning parsing on stdout is not reliable at all.
That is a problem Nmstate can solve! Nmstate is a library with an accompanying command line tool that manages host networking settings in a declarative manner. The networking state is described by a pre-defined schema. Reporting of current state and changes to it (desired state) both conform to the schema.
As an example here is a YAML defining the configuration of an ethernet interface with an IPv4 address.
```yaml!
interfaces:
- name: eth0
type: ethernet
state: up
ipv4:
address:
- ip: 192.168.122.250
prefix-length: 24
enabled: true
```
## Hands-on on a real scenario
Working on a practical example that represents real-world usecases are always more rewarding. The example I propose is the usage of link aggregation to aggregate all the ethernet interfaces of your host. This configuration can be used to perform load-balancing or improve the network resilience to physical failures.
At the end of the blog, we will end up having a linux bond interface configured with all the ethernet interfaces attached to it.
## Understanding current vs desired state
Nmstate is not only able to configure the networking on the host but also to report the current configuration. This is where the potential of Nmstate is clearly visible for first time, you can fetch the current state and modify it as you wish to then apply it and get your changes into the system.
The code snippet below manages the necessary imports and code to fetch the current state using Nmstate.
```rust!
use nmstate::NetworkState;
// This is the main function where all the action will happen.
fn main() {
let mut current_state = NetworkState::new();
// we need to call unwrap() because retrieve might fail
// we just hope it won't
current_state.retrieve().unwrap();
}
```
The state is composed of four main sections; routes, dns, route rules and interfaces. In this blog article we are going to focus mainly on the interfaces section.
## Creating a new bond interface
Link-aggregation in Linux is frequently implemented by Bond interfaces. They are widely known and supported. The kernel API is also really extense and allow the users to perform fine-grain tunning. Luckly, Nmstate supports all the fine-grain tunning that kernel does! For more information about bond interfaces I recommend you the [official kernel documentation](https://www.kernel.org/doc/Documentation/networking/bonding.rst).
In order to accomplish our purpose, we are going to extend the code snippet from below to create a bond interface and add it to the desired state.
```rust!
fn main() {
// insert code from last snippet
let mut bond_iface = BondInterface::new();
// We define the interface name, type and state
// there are more BaseInterface properties that could
// be set but we will stick with defaults for the sake of
// simplicity
bond_iface.base.name = "bond0".to_string();
bond_iface.base.iface_type = InterfaceType::Bond;
bond_iface.base.state = InterfaceState::Up;
// In addition, we are going to create a BondConfig
// struct with some options
let mut bond_config = BondConfig::new();
// We are going to set ActiveBackup as a mode because
// we want the ports to act as a fallback
bond_config.mode = Some(BondMode::ActiveBackup);
}
```
## Adding all the ethernet interfaces as ports
So, once the bond interface is created, we must attach the needed ports to it. We want this program to run on different environments where we don't know how many ethernet interfaces are there or their names.
For each interface we are going to check the type and if it matches our desired type, we are attaching it to the bond interface.
```rust!
// insert code from last snippets
// Now let's iterate over all the interfaces filtering The
// ones that are not ethernet and adding their name to the
// port list in the bond config
for iface in current_state.interfaces.iter() {
match iface.iface_type() {
InterfaceType::Ethernet => {
port_list.push(iface.name().to_string());
}
_ => {
// ignore other kind of interfaces
}
}
}
```
## Applying the state
Now it is time to apply the state. The configuration is currently only on the data structures we created but not on the system. In order to make it real on the host configuration we must apply the NetworkState we crafted.
```rust!
fn main() {
// insert code from last snippets
// The structs allocated below need to be set to the BondConfig
// and BondInterface ones.
bond_config.port = Some(port_list);
bond_iface.bond = Some(bond_config);
// Now we need to create the desired state and populate it with
// the bond interface
let mut desired_state = NetworkState::new();
desired_state.interfaces.push(Interface::Bond(bond_iface));
// We are ready to apply the configuration to the system, as before,
// we are ignoring all the errors.
desired_state.apply().unwrap();
}
```
## Validation, verification and rollback
When applying the state multiple things happens and it is important to be aware of them so you can take the advantage!
Initially, the desired state is validated. That means, Nmstate checks for incompatible states or configuration. In this phase, an error will be raised if the user is trying to configure something clearly wrong.
After that, the configuration is applied on NetworkManager and therefore on the system. Once finished, Nmstate will verify that it happened. That means, it will gather the current state and will compare it with the desired state provided. If they mismatch, an error will be raised and a rollback will be performed so connectivity can be restored if lost.
Finally is everything goes well the function will return an unit.
## Conclusion
In essence, something that would require a long and complex bash script is done in a readable Rust script. All of this can also be done in other languages like Python or Golang as Nmstate provides the needed bindings.
The final script would look like this.
```rust!
use nmstate::{
BondConfig, BondInterface, BondMode, Interface,
InterfaceState, InterfaceType, NetworkState
};
// This is the main function where all the action will take place.
fn main() {
let mut current_state = NetworkState::new();
current_state.retrieve().unwrap();
let mut bond_iface = BondInterface::new();
// We define the interface name, type and state
// there are more BaseInterface properties that could
// be set but we will stick with defaults for the sake of
// simplicity
bond_iface.base.name = "bond0".to_string();
bond_iface.base.iface_type = InterfaceType::Bond;
bond_iface.base.state = InterfaceState::Up;
// In addition, we are going to create a BondConfig
// struct with some options
let mut bond_config = BondConfig::new();
// We are going to set ActiveBackup as a mode because
// we want the ports to act as a fallbacks
bond_config.mode = Some(BondMode::ActiveBackup);
// We are going to create a port list that we are
// going to use later in bond config
let mut port_list: Vec<String> = Vec::new();
// Now let's iterate over all the interfaces filtering The
// ones that are not ethernet and adding their name to the
// port list in the bond config
for iface in current_state.interfaces.iter() {
match iface.iface_type() {
InterfaceType::Ethernet => {
port_list.push(iface.name().to_string());
}
_ => {
// ignore other kind of interfaces
}
}
}
// The structs allocated below need to be set to the BondConfig
// and BondInterface ones.
bond_config.port = Some(port_list);
bond_iface.bond = Some(bond_config);
// Now we need to create the desired state and populate it with the
// bond interface
let mut desired_state = NetworkState::new();
desired_state.interfaces.push(Interface::Bond(bond_iface));
// We are ready to apply the configuration to the system, as before,
// we are ignoring all the errors.
desired_state.apply().unwrap();
}
```