Skip to content

Commit

Permalink
Expose the networking API over HTTP (#1064)
Browse files Browse the repository at this point in the history
# Implementation PBI description
---

In the previous PBI we documented a possible API and also created a POC
but it just allowed to query some configuration, so we have extended the
PR with the API implementation

## Implemented Network API

<details>
  <summary>Resource <b>/network/state</b></summary>
  
  - GET: get the current network state and general information 
  
  **Request example**
  
  ```json
$ curl -s localhost:3000/api/network/state -H @headers.txt | j
{
  "connectivity": true,
  "wireless_enabled": false,
  "networking_enabled": true
}
```

  - PUT: Update the current network state (currently only enabling wireless is supported)
  
  **Request example**

  ```json

$ cat state.json 
{
  "connectivity": true,
  "wireless_enabled": true,
  "networking_enabled": true
}
  
$ curl -s -X POST localhost:3000/api/network/state -H @headers.txt -H "Content-Type: application/json" -d @state.json  | jq
{
  "connectivity": true,
  "wireless_enabled": true,
  "networking_enabled": true
}
```
  
</details>
  
<details>
  <summary>Resource <b>/network/devices</b></summary>
  
  - GET: list of known devices
    - Attributes
      - Name: String
      - Type: Enum


**Request example**

```json
$ curl -s localhost:3000/network/devices -H @headers.txt | jq
[
  {
    "name": "enp2s0f0",
    "type": "Ethernet"
  },
  {
    "name": "wlp3s0",
    "type": "Wireless"
  },
  {
    "name": "eth0",
    "type": "Ethernet"
  }
 ``` 
</details>

<details>

<summary>Resource <b>/network/connections</b></summary>

  - GET: list of managed / known connections
  **Request example**
  
  ```json
$curl -s localhost:3000/api/network/connections -H @headers.txt | jq
[
  {
    "id": "lo",
    "method4": "manual",
    "method6": "manual",
    "addresses": [
      "127.0.0.1/8",
      "::1"
    ],
    "interface": "lo"
  },
  {
    "id": "Wired connection 1",
    "method4": "manual",
    "gateway4": "192.168.0.1",
    "method6": "auto",
    "addresses": [
      "192.168.0.230/24"
    ],
    "nameservers": [
      "192.168.0.1"
    ],
    "interface": "eth0"
  },
  {
    "id": "AgamaNetwork",
    "method4": "disabled",
    "method6": "disabled",
    "wireless": {
      "password": "agama.test",
      "security": "wpa-psk",
      "ssid": "AgamaNetwork2",
      "mode": "infrastructure"
    }
  }
]
```

 - POST: create a new connection
 ** Request example **

```json
$ curl -X POST localhost:3000/api/network/connections -H @headers.txt -H "Content-Type: application/json" -d @data_wifi.json
"ce91e1b8-7da4-4bff-a286-91610a8cb762"
```

####  /network/connections/:id
  - GET: find a connection by its uuid or id **(not implemented)**
  - PATCH: update connection
  - DELETE: remove the connection

The attributes for updating a connection or creating a new one could
follow the same schema used for the profile:

```json
      "connections": {
        "description": "Network connections to be defined",
        "type": "array",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "id": {
              "description": "Connection ID",
              "type": "string"
            },
            "interface": {
              "description": "The name of the network interface bound to this connection",
              "type": "string"
            },
            "mac-address": {
              "description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')",
              "type": "string"
            },
            "method4": {
              "description": "IPv4 configuration method (e.g., 'auto')",
              "type": "string",
              "enum": [
                "auto",
                "manual",
                "link-local",
                "disabled"
              ]
            },
            "method6": {
              "description": "IPv6 configuration method (e.g., 'auto')",
              "type": "string",
              "enum": [
                "auto",
                "manual",
                "link-local",
                "disabled"
              ]
            },
            "gateway4": {
              "description": "Connection gateway address (e.g., '192.168.122.1')",
              "type": "string"
            },
            "gateway6": {
              "description": "Connection gateway address (e.g., '::ffff:c0a8:7a01')",
              "type": "string"
            },
            "addresses": {
              "type": "array",
              "items": {
                "description": "Connection addresses",
                "type": "string",
                "additionalProperties": false
              }
            },
            "nameservers": {
              "type": "array",
              "items": {
                "description": "Nameservers (IPv4 and/or IPv6 are allowed)",
                "type": "string",
                "additionalProperties": false
              }
            },
            "wireless": {
              "type": "object",
              "description": "Wireless configuration",
              "additionalProperties": false,
              "properties": {
                "password": {
                  "type": "string"
                },
                "security": {
                  "type": "string"
                },
                "ssid": {
                  "type": "string"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "infrastructure",
                    "adhoc",
                    "mesh",
                    "ap"
                  ]
                }
              }
            },
            "bond": {
              "type": "object",
              "description": "Bonding configuration",
              "additionalProperties": false,
              "properties": {
                "mode": {
                  "type": "string"
                },
                "options": {
                  "type": "string"
                },
                "ports": {
                  "type": "array",
                  "items": {
                    "description": "A list of the interfaces or connections to be bonded",
                    "type": "string",
                    "additionalProperties": false
                  }
                }
              }
            },
            "match": {
              "type": "object",
              "description": "Match settings",
              "additionalProperties": false,
              "properties": {
                "kernel": {
                  "type": "array",
                  "items": {
                    "description": "A list of kernel command line arguments to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "interface": {
                  "type": "array",
                  "items": {
                    "description": "A list of interface names to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "driver": {
                  "type": "array",
                  "items": {
                    "description": "A list of driver names to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "path": {
                  "type": "array",
                  "items": {
                    "description": "A list of paths to match against the ID_PATH udev property of devices",
                    "type": "string",
                    "additionalProperties": false
                  }
                }
              }
            }
          },
          "required": [
            "id"
          ]
```

Which means that for bringing up or down an specific connection / device
could need an specific method or it could be handle as any other
attribute.
  
  </details>

<details>
  <summary>Resource <b>/network/wifi</b></summary>

  - GET: list of scanned WiFi networks
  **Request example**

```json
$curl -s localhost:3000/api/network/wifi -H @headers.txt | jq
[
  "Agama",
  "Agama2"
]

```

</details>

<details>
  <summary>Resource <b>/network/system/apply</b></summary>

  - PUT: Apply the changes to the system
  **Request example**

```json
curl -s -X PUT localhost:3000/api/network/system/apply -H @headers.txt | jq
null
```
</details>

## Testing

<details>
  <summary><b>Tested manually</b></summary>

```bash
suse@vikingo-laptop:~$ ./generate_headers.sh 
Obtained token 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTEwMzU4MDV9.nifdrxlq3ZKK4LoVjY-2sPb9ZzXIq5M3Xxqudy8zqvE'
suse@vikingo-laptop:~$ ./get_state.sh 
{
  "connectivity": true,
  "wireless_enabled": true,
  "networking_enabled": true
}
suse@vikingo-laptop:~$ ./get_connections.sh 
[
  {
    "id": "lo",
    "method4": "manual",
    "method6": "manual",
    "addresses": [
      "127.0.0.1/8",
      "::1"
    ],
    "interface": "lo"
  },
  {
    "id": "Wired connection 1",
    "method4": "manual",
    "gateway4": "192.168.0.1",
    "method6": "auto",
    "addresses": [
      "192.168.0.230/24"
    ],
    "nameservers": [
      "192.168.0.1"
    ],
    "interface": "eth0"
  },
  {
    "id": "AgamaNetwork",
    "method4": "disabled",
    "method6": "disabled",
    "wireless": {
      "password": "agama.test",
      "security": "wpa-psk",
      "ssid": "AgamaNetwork2",
      "mode": "infrastructure"
    }
  }
]
suse@vikingo-laptop:~$ ./get_wifis.sh 
[
  "AgamaNetwork",
  "AgamaNetwork2",
]

<details>
  <summary>Resource <b>/network/system/apply</b></summary>
  - PUT: Appl  list of scanned WiFi networks
  **Request example**
curl -s -X PUT localhost:3000/api/network/system/apply -H @headers.txt | jq

# Scripts used for obtain and modify the configuration

suse@vikingo-laptop:~$ cat generate_headers.sh 
#!/bin/bash

TOKEN=$(curl -s -X POST localhost:3000/api/auth -H 'Content-Type: application/json' -d '{"password": "agama.auth"}' | jq -r ".token")
echo "Obtained token '${TOKEN}'"

echo -n "Authorization: Bearer " >headers.txt
echo $TOKEN >>headers.txt
suse@vikingo-laptop:~$ cat get_connections.sh 
#!/bin/bash

curl -s localhost:3000/api/network/connections -H @headers.txt | jq
suse@vikingo-laptop:~$ cat get_wifis.sh 
#!/bin/bash

curl -s localhost:3000/api/network/wifi -H @headers.txt | jq
suse@vikingo-laptop:~$ cat get_state.sh 
#!/bin/bash

curl -s localhost:3000/api/network/state -H @headers.txt | jq
cat change_state.sh 
#!/bin/bash

curl -X POST localhost:3000/api/network/state -H @headers.txt -H "Content-Type: application/json" -d @state.json


```

</details>

# Documentation PBI description
---

## Problem

We would like to expose the Networking API over http which is currently
not available.

## Solution

Document an HTTP alternative as well as provide a POC exposing it.

## API Design

Our Rest API could expose the general state and configuration of the
network as well as the devices and connections resources below the
network namespace as shown below:

<details>
  <summary>Resource <b>/network/state</b></summary>
  
  - GET: get the current network state and general information 
  
</details>

<details>
  <summary>Resource <b>/network/config</b></summary>
  
  - GET: get the general configuration like wireless enabled or not
  - PATCH: update general configuration
  
</details>
  
<details>
  <summary>Resource <b>/network/devices</b></summary>
  
  - GET: list of known devices
    - Attributes
      - Name: String
      - Type: Enum

#### /network/devices/:name
  - GET: find a device by its name

**Request example**

```json
$ curl -s localhost:3000/network/devices -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq
[
  {
    "name": "enp2s0f0",
    "type": "Ethernet"
  },
  {
    "name": "wlp3s0",
    "type": "Wireless"
  },
  {
    "name": "eth0",
    "type": "Ethernet"
  }
 ``` 
</details>

<details>

<summary>Resource <b>/network/connections</b></summary>

  - GET: list of managed / known connections
  - POST: create a new connection
  **Request example**
  
  ```json
  suse@vikingo-laptop:~$ curl -s localhost:3000/network/connections -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq
[
  {
    "id": "Wired connection 1",
    "uuid": "530d40c2-c580-43a7-b93e-c443702529a2",
    "mac_address": "",
    "ip_config": {
      "method4": "Manual",
      "method6": "Auto",
      "addresses": [
        "192.168.0.230/24"
      ],
      "nameservers": [
        "192.168.0.1"
      ],
      "gateway4": "192.168.0.1",
      "routes4": [
        {
          "destination": "0.0.0.0/24",
          "next_hop": "192.168.0.1"
        }
      ],
      "routes6": []
    },
    "status": "Up",
    "interface": "eth0",
    "port_config": "None",
    "match_config": {},
    "config": "Ethernet"
  },
  {
    "id": "AgamaTest",
    "uuid": "e7c684d5-f8e9-43a0-b303-decfe2883e53",
    "mac_address": "",
    "ip_config": {
      "method4": "Auto",
      "method6": "Auto",
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "wlp3s0",
    "port_config": "None",
    "match_config": {},
    "config": {
      "Wireless": {
        "mode": "Infra",
        "ssid": "AgamaTest",
        "security": "WPA2",
        "wep_security": {
          "auth_alg": "Open",
          "wep_key_type": "Unknown",
          "wep_key_index": 0
        },
        "hidden": false
      }
    }
  },
  {
    "id": "AgamaTest2",
    "uuid": "bc323ed0-18d8-4f85-b90e-5b7907e3b711",
    "mac_address": "",
    "ip_config": {
      "method4": "Auto",
      "method6": "Auto",
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "wlp3s0",
    "port_config": "None",
    "match_config": {},
    "config": {
      "Wireless": {
        "mode": "Infra",
        "ssid": "AgamaTest2",
        "security": "WPA2",
        "wep_security": {
          "auth_alg": "Open",
          "wep_key_type": "Unknown",
          "wep_key_index": 0
        },
        "hidden": false
      }
    }
  },
  {
    "id": "lo",
    "uuid": "66d7c54a-735a-42c6-b412-572a1134efec",
    "mac_address": "",
    "ip_config": {
      "method4": "Manual",
      "method6": "Manual",
      "addresses": [
        "127.0.0.1/8",
        "::1"
      ],
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "lo",
    "port_config": "None",
    "match_config": {},
    "config": "Loopback"
  }
]
```

####  /network/connections/:id
  - GET: find a connection by its uuid or id
  - PATCH: update connection
  - DELETE: remove the connection

The attributes for updating a connection or creating a new one could
follow the same schema used for the profile:

```json
      "connections": {
        "description": "Network connections to be defined",
        "type": "array",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "id": {
              "description": "Connection ID",
              "type": "string"
            },
            "interface": {
              "description": "The name of the network interface bound to this connection",
              "type": "string"
            },
            "mac-address": {
              "description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')",
              "type": "string"
            },
            "method4": {
              "description": "IPv4 configuration method (e.g., 'auto')",
              "type": "string",
              "enum": [
                "auto",
                "manual",
                "link-local",
                "disabled"
              ]
            },
            "method6": {
              "description": "IPv6 configuration method (e.g., 'auto')",
              "type": "string",
              "enum": [
                "auto",
                "manual",
                "link-local",
                "disabled"
              ]
            },
            "gateway4": {
              "description": "Connection gateway address (e.g., '192.168.122.1')",
              "type": "string"
            },
            "gateway6": {
              "description": "Connection gateway address (e.g., '::ffff:c0a8:7a01')",
              "type": "string"
            },
            "addresses": {
              "type": "array",
              "items": {
                "description": "Connection addresses",
                "type": "string",
                "additionalProperties": false
              }
            },
            "nameservers": {
              "type": "array",
              "items": {
                "description": "Nameservers (IPv4 and/or IPv6 are allowed)",
                "type": "string",
                "additionalProperties": false
              }
            },
            "wireless": {
              "type": "object",
              "description": "Wireless configuration",
              "additionalProperties": false,
              "properties": {
                "password": {
                  "type": "string"
                },
                "security": {
                  "type": "string"
                },
                "ssid": {
                  "type": "string"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "infrastructure",
                    "adhoc",
                    "mesh",
                    "ap"
                  ]
                }
              }
            },
            "bond": {
              "type": "object",
              "description": "Bonding configuration",
              "additionalProperties": false,
              "properties": {
                "mode": {
                  "type": "string"
                },
                "options": {
                  "type": "string"
                },
                "ports": {
                  "type": "array",
                  "items": {
                    "description": "A list of the interfaces or connections to be bonded",
                    "type": "string",
                    "additionalProperties": false
                  }
                }
              }
            },
            "match": {
              "type": "object",
              "description": "Match settings",
              "additionalProperties": false,
              "properties": {
                "kernel": {
                  "type": "array",
                  "items": {
                    "description": "A list of kernel command line arguments to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "interface": {
                  "type": "array",
                  "items": {
                    "description": "A list of interface names to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "driver": {
                  "type": "array",
                  "items": {
                    "description": "A list of driver names to match",
                    "type": "string",
                    "additionalProperties": false
                  }
                },
                "path": {
                  "type": "array",
                  "items": {
                    "description": "A list of paths to match against the ID_PATH udev property of devices",
                    "type": "string",
                    "additionalProperties": false
                  }
                }
              }
            }
          },
          "required": [
            "id"
          ]
```

Which means that for bringing up or down an specific connection / device
could need an specific method or it could be handle as any other
attribute.
  
  </details>

<details>
  <summary>Resource <b>/network/wifi_networks</b></summary>

  - GET: list of scanned wifi networks

</details>

**Note:** Which attributes should be required and exposed needs to be
discussed, and probably we should expose the persistent and running
configuration maybe as part of the same resource or separately.

<details>
<summary>Example of the <b>OpenAPI</b> generated documentation based on
the implemented <b>POC code</b></summary>

```json
{
  "openapi": "3.0.3",
  "info": {
    "title": "agama-dbus-server",
    "description": "Agama web API description",
    "license": {
      "name": ""
    },
    "version": "0.1.0"
  },
  "paths": {
    "/network/connections": {
      "get": {
        "tags": [
          "crate::network::web"
        ],
        "operationId": "connections",
        "responses": {
          "200": {
            "description": "List of known connections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Connection"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/network/devices": {
      "get": {
        "tags": [
          "crate::network::web"
        ],
        "operationId": "devices",
        "responses": {
          "200": {
            "description": "List of devices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Device"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Connection": {
        "type": "object",
        "description": "Represents a known network connection.",
        "required": [
          "id",
          "uuid",
          "mac_address",
          "ip_config",
          "status",
          "port_config",
          "match_config",
          "config"
        ],
        "properties": {
          "config": {
            "$ref": "#/components/schemas/ConnectionConfig"
          },
          "controller": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Uuid"
              }
            ],
            "nullable": true
          },
          "id": {
            "type": "string"
          },
          "interface": {
            "type": "string",
            "nullable": true
          },
          "ip_config": {
            "$ref": "#/components/schemas/IpConfig"
          },
          "mac_address": {
            "$ref": "#/components/schemas/MacAddress"
          },
          "match_config": {
            "$ref": "#/components/schemas/MatchConfig"
          },
          "port_config": {
            "$ref": "#/components/schemas/PortConfig"
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "uuid": {
            "$ref": "#/components/schemas/Uuid"
          }
        }
      },
      "Device": {
        "type": "object",
        "description": "Network device",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/DeviceType"
          }
        }
      },
      "DeviceType": {
        "type": "string",
        "enum": [
          "Loopback",
          "Ethernet",
          "Wireless",
          "Dummy",
          "Bond",
          "Vlan",
          "Bridge"
        ]
      },
      "NetworkState": {
        "type": "object",
        "required": [
          "devices",
          "connections"
        ],
        "properties": {
          "connections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Connection"
            }
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Device"
            }
          }
        }
      }
    }
  }
}
```

</details>

### Signals / Websocket notifications

With **websockets** we can carry more information than what is usually
emitted over **DBUS** therefore we could subscribe to **DBUS** signals
in the backend for the different resources submitting more information
over the websocket, for example for any new device connected or any
change in a connection property but have not gone over what to notify so
far as we are not subscribing to signals in the backend at all and we
are only sending when a connections has been added or removed.

## Testing

<details>
  <summary><b>Tested manually</b></summary>

```bash
$ curl -s -X POST localhost:3000/authenticate -H 'Content-Type: application/json' -d '{"password": "your_password"}' | jq -r ".token"

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y

$ curl -s localhost:3000/network/devices -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq
[
  {
    "name": "enp2s0f0",
    "type": "Ethernet"
  },
  {
    "name": "wlp3s0",
    "type": "Wireless"
  },
  {
    "name": "eth0",
    "type": "Ethernet"
  }
suse@vikingo-laptop:~$ curl -s localhost:3000/network/connections -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq
[
  {
    "id": "Wired connection 1",
    "uuid": "530d40c2-c580-43a7-b93e-c443702529a2",
    "mac_address": "",
    "ip_config": {
      "method4": "Manual",
      "method6": "Auto",
      "addresses": [
        "192.168.0.230/24"
      ],
      "nameservers": [
        "192.168.0.1"
      ],
      "gateway4": "192.168.0.1",
      "routes4": [
        {
          "destination": "0.0.0.0/24",
          "next_hop": "192.168.0.1"
        }
      ],
      "routes6": []
    },
    "status": "Up",
    "interface": "eth0",
    "port_config": "None",
    "match_config": {},
    "config": "Ethernet"
  },
  {
    "id": "AgamaTest",
    "uuid": "e7c684d5-f8e9-43a0-b303-decfe2883e53",
    "mac_address": "",
    "ip_config": {
      "method4": "Auto",
      "method6": "Auto",
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "wlp3s0",
    "port_config": "None",
    "match_config": {},
    "config": {
      "Wireless": {
        "mode": "Infra",
        "ssid": "AgamaTest",
        "security": "WPA2",
        "wep_security": {
          "auth_alg": "Open",
          "wep_key_type": "Unknown",
          "wep_key_index": 0
        },
        "hidden": false
      }
    }
  },
  {
    "id": "AgamaTest2",
    "uuid": "bc323ed0-18d8-4f85-b90e-5b7907e3b711",
    "mac_address": "",
    "ip_config": {
      "method4": "Auto",
      "method6": "Auto",
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "wlp3s0",
    "port_config": "None",
    "match_config": {},
    "config": {
      "Wireless": {
        "mode": "Infra",
        "ssid": "AgamaTest2",
        "security": "WPA2",
        "wep_security": {
          "auth_alg": "Open",
          "wep_key_type": "Unknown",
          "wep_key_index": 0
        },
        "hidden": false
      }
    }
  },
  {
    "id": "lo",
    "uuid": "66d7c54a-735a-42c6-b412-572a1134efec",
    "mac_address": "",
    "ip_config": {
      "method4": "Manual",
      "method6": "Manual",
      "addresses": [
        "127.0.0.1/8",
        "::1"
      ],
      "routes4": [],
      "routes6": []
    },
    "status": "Up",
    "interface": "lo",
    "port_config": "None",
    "match_config": {},
    "config": "Loopback"
  }
]

```

</details>
  • Loading branch information
teclator committed Mar 22, 2024
2 parents f02dc7d + de963bd commit bc95d15
Show file tree
Hide file tree
Showing 19 changed files with 1,075 additions and 84 deletions.
19 changes: 16 additions & 3 deletions rust/agama-lib/src/network/types.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use cidr::errors::NetworkParseError;
use serde::{Deserialize, Serialize};
use std::{fmt, str};
use std::{
fmt,
str::{self, FromStr},
};
use thiserror::Error;
use zbus;

/// Network device
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub struct Device {
pub name: String,
pub type_: DeviceType,
Expand All @@ -25,13 +30,21 @@ impl fmt::Display for SSID {
}
}

impl FromStr for SSID {
type Err = NetworkParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SSID(s.as_bytes().into()))
}
}

impl From<SSID> for Vec<u8> {
fn from(value: SSID) -> Self {
value.0
}
}

#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub enum DeviceType {
Loopback = 0,
Ethernet = 1,
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ tokio-stream = "0.1.14"
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
regex = "1.10.2"
once_cell = "1.18.0"
macaddr = "1.0"
macaddr = { version = "1.0", features = ["serde_std"] }
async-trait = "0.1.75"
axum = { version = "0.7.4", features = ["ws"] }
serde_json = "1.0.113"
Expand Down
1 change: 1 addition & 0 deletions rust/agama-server/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod error;
pub mod model;
mod nm;
pub mod system;
pub mod web;

pub use action::Action;
pub use adapter::{Adapter, NetworkAdapterError};
Expand Down
32 changes: 26 additions & 6 deletions rust/agama-server/src/network/action.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::network::model::Connection;
use crate::network::model::{AccessPoint, Connection, Device};
use agama_lib::network::types::DeviceType;
use tokio::sync::oneshot;
use uuid::Uuid;
use zbus::zvariant::OwnedObjectPath;

use super::{error::NetworkStateError, NetworkAdapterError};
use super::{error::NetworkStateError, model::GeneralState, NetworkAdapterError};

pub type Responder<T> = oneshot::Sender<T>;
pub type ControllerConnection = (Connection, Vec<String>);
Expand All @@ -21,11 +21,18 @@ pub enum Action {
DeviceType,
Responder<Result<OwnedObjectPath, NetworkStateError>>,
),
/// Gets a connection
/// Add a new connection
NewConnection(
Connection,
Responder<Result<OwnedObjectPath, NetworkStateError>>,
),
/// Gets a connection by its Uuid
GetConnection(Uuid, Responder<Option<Connection>>),
/// Gets a connection
GetConnections(Responder<Vec<Connection>>),
/// Gets a connection path
GetConnectionPath(Uuid, Responder<Option<OwnedObjectPath>>),
/// Gets a connection
/// Gets a connection path by id
GetConnectionPathById(String, Responder<Option<OwnedObjectPath>>),
/// Get connections paths
GetConnectionsPaths(Responder<Vec<OwnedObjectPath>>),
Expand All @@ -34,19 +41,32 @@ pub enum Action {
Uuid,
Responder<Result<ControllerConnection, NetworkStateError>>,
),
/// Gets all scanned access points
GetAccessPoints(Responder<Vec<AccessPoint>>),
/// Gets a device by its name
GetDevice(String, Responder<Option<Device>>),
/// Gets all the existent devices
GetDevices(Responder<Vec<Device>>),
/// Gets a device path
GetDevicePath(String, Responder<Option<OwnedObjectPath>>),
/// Get devices paths
GetDevicesPaths(Responder<Vec<OwnedObjectPath>>),
GetGeneralState(Responder<GeneralState>),
/// Sets a controller's ports. It uses the Uuid of the controller and the IDs or interface names
/// of the ports.
SetPorts(
Uuid,
Box<Vec<String>>,
Responder<Result<(), NetworkStateError>>,
),
/// Update a connection (replacing the old one).
/// Updates a connection (replacing the old one).
UpdateConnection(Box<Connection>),
/// Updates the general network configuration
UpdateGeneralState(GeneralState),
/// Forces a wireless networks scan refresh
RefreshScan(Responder<Result<(), NetworkAdapterError>>),
/// Remove the connection with the given Uuid.
RemoveConnection(Uuid),
RemoveConnection(Uuid, Responder<Result<(), NetworkStateError>>),
/// Apply the current configuration.
Apply(Responder<Result<(), NetworkAdapterError>>),
}
3 changes: 2 additions & 1 deletion rust/agama-server/src/network/adapter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::network::model::StateConfig;
use crate::network::NetworkState;
use agama_lib::error::ServiceError;
use async_trait::async_trait;
Expand All @@ -16,7 +17,7 @@ pub enum NetworkAdapterError {
/// A trait for the ability to read/write from/to a network service
#[async_trait]
pub trait Adapter {
async fn read(&self) -> Result<NetworkState, NetworkAdapterError>;
async fn read(&self, config: StateConfig) -> Result<NetworkState, NetworkAdapterError>;
async fn write(&self, network: &NetworkState) -> Result<(), NetworkAdapterError>;
}

Expand Down
7 changes: 5 additions & 2 deletions rust/agama-server/src/network/dbus/interfaces/connections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Connections {
impl Connections {
/// Creates a Connections interface object.
///
/// * `objects`: Objects paths registry.
/// * `actions`: sending-half of a channel to send actions.
pub fn new(actions: UnboundedSender<Action>) -> Self {
Self {
actions: Arc::new(Mutex::new(actions)),
Expand Down Expand Up @@ -101,7 +101,10 @@ impl Connections {
.parse()
.map_err(|_| NetworkStateError::InvalidUuid(uuid.to_string()))?;
let actions = self.actions.lock().await;
actions.send(Action::RemoveConnection(uuid)).unwrap();
let (tx, rx) = oneshot::channel();
actions.send(Action::RemoveConnection(uuid, tx)).unwrap();

rx.await.unwrap()?;
Ok(())
}

Expand Down
10 changes: 10 additions & 0 deletions rust/agama-server/src/network/dbus/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ impl Tree {
self.objects.devices_paths()
}

pub fn device_path(&self, name: &str) -> Option<OwnedObjectPath> {
self.objects.device_path(name).map(|o| o.into())
}

/// Returns all connection paths.
pub fn connections_paths(&self) -> Vec<OwnedObjectPath> {
self.objects.connections_paths()
Expand Down Expand Up @@ -237,6 +241,12 @@ impl ObjectsRegistry {
path
}

/// Returns the path for a device.
///
/// * `name`: device name.
pub fn device_path(&self, name: &str) -> Option<ObjectPath> {
self.devices.get(name).map(|p| p.as_ref())
}
/// Returns the path for a connection.
///
/// * `uuid`: connection ID.
Expand Down

0 comments on commit bc95d15

Please sign in to comment.