NetworkManagerの接続設定にカスタム属性を追加する際のTips

# TL; DR

キー値には、 `.` 文字（ドット）を追加するのが必須です。

設定ファイル（ `NetworkManager.conf` など）の場合、userセクションにキー／バリューを追加します。

```ini:userセクションにカスタム属性を追加した例
[user]
example.conf=1
example.desc="This is example description"
```

python-networkmanagerモジュールを使う場合は、userセクションにdataキーを入れ、dataキーの値として辞書型の設定を入れてください。

```py:設定値にカスタム属性を追加した例
import NetworkManager

connection = {
    "user": {
        "data": {
            "example.conf": 1,
            "example.desc": "This is example description",
        },
    },
}
```

ちなみに、2019年8月4日現在、 `nmcli` を利用したカスタム属性の設定は未実装です。



# はじめに

皆さんも、ネットワーク接続設定をする際はNetworkManagerを利用していると思います。

> NetworkManager Reference Manual: NetworkManager Reference Manual
> https://developer.gnome.org/NetworkManager/stable/

その際に、ネットワーク設定ごとにカスタム属性を追加したいと思ったこともあるでしょう。

NetworkManagerにはカスタム属性を追加する項目として、userセクションが用意されています。

> user: NetworkManager Reference Manual
> https://developer.gnome.org/NetworkManager/stable/settings-user.html

また、userセクションにはプロパティとして、dataキーを文字列：文字列の辞書型として設定できます。

> A dictionary of key/value pairs with user data. This data is ignored by NetworkManager and can be used at the users discretion. The keys only support a strict ascii format, but the values can be arbitrary UTF8 strings up to a certain length.
>
> 意訳：ユーザーデータにおけるキー／バリューをペアに持つ辞書型である。NetworkManagerはこの値を無視し、ユーザーの判断で利用できる。キーには厳密なASCIIフォーマットのみをサポートするが、値は特定の長さまでの任意のUTF-8文字列を設定できる。

さて、後はこれを利用すればよいだけなのですが......
実はちょっとした難しさが残っています。

本記事では、その「ちょっとした難しさ」を解決します。



# 問題解決への道のり

userセクションを利用して設定しようとした場合、次の問題があります。

* どのように辞書型を設定するのか不明である
* NetworkManagerを介してカスタム属性を取得できるのか不明である
* 謎のエラーによって阻まれてしまう

以降、各問題に対応します。


## どのように辞書型を設定するのか不明である

`nmcli` を用いてWiFi接続をした方はわかると思うのですが、簡単な設定でも以下の項目が必要（WPA2-PSK方式の場合）となります。

- SSID名
- パスワード

例えば、SSID名「ExampleSSID」、パスワード「P@ssw0rd」のSSIDに接続する設定は、次のコマンドを利用します。

```sh:nmcliを用いたWiFi接続設定例
nmcli d wifi connect ExampleSSID password P@ssw0rd
```

すると不思議なことに、NetworkManagerの設定ファイルが作成されます。

```ini:WiFi接続設定例の設定ファイル
[connection]
id=ExampleSSID
type=802-11-wireless
uuid=2815492f-7e56-435e-b2e9-246bd7cdc664

[802-11-wireless]
ssid=ExampleSSID

[802-11-wireless-security]
key-mgmt=wpa-psk
psk=P@ssw0rd
```

また、この設定ファイルは、下記のマニュアルページにおける各セクション、キーおよびバリュー型の値と一致していることがわかります。

> Part II. Network Configuration Setting Specification: NetworkManager Reference Manual
> https://developer.gnome.org/NetworkManager/stable/ref-settings.html

例えば下記を見ると、connectionセクションにおけるtypeキーの値はstring型なのだな、ということがわかります。

> connection: NetworkManager Reference Manual
> https://developer.gnome.org/NetworkManager/stable/settings-connection.html

同様に、userセクションにおけるdataキーの値が「dict of string to string」型であることもわかります。

> user: NetworkManager Reference Manual
> https://developer.gnome.org/NetworkManager/stable/settings-user.html

しかし、この「dict of string to string」型をどのように設定ファイルに追加すればよいのかについては、どこにも記載がありません。

結局、python-networkmanagerを用いて設定した後の設定ファイルを見て、どのように設定されるのかを確認しました。
これについては後程説明します。

結論から言うと、下記のように設定します。
各キー値とバリュー値を1行ずつ設定し、dataというキー名は記述しません。
また、キー値には `.` 文字（ドット）を必ず設定してください。
バリュー値についてはUTF-8の規定長未満の文字列であれば問題ないらしいです。

```ini:userセクションにカスタム属性を追加した例
[user]
example.conf=1
example.desc="This is example description"
```



## NetworkManagerを介してカスタム属性を取得できるのか不明である

これについては、特に問題なく取得できます。

そもそもどうして気になっていたのかというと、下記の文言が説明にあったためです。

> This data is ignored by NetworkManager and can be used at the users discretion.
>
> 意訳：NetworkManagerはこの値を無視し、ユーザーの判断で利用できる。

これは、ファイルに記述しても全くデータとして取得できない、という意味ではありません。
単純に、ネットワーク設定時には利用しない、という意味です。

その証拠に、python-networkmanagerを利用してカスタム属性を取得できます。

```py:設定値を取得する例
import NetworkManager

def print_user_data():
    """
    example
    -------
    >>> print_user_data()
    {"example.conf": 1, "example.desc": "This is example description"}
    """
    for conn in NetworkManager.Settings.ListConnections():
        user = conn.GetSettings()["user"]
        if user is not None:
            print(user["data"])
```



## 謎のエラーによって阻まれてしまう

何よりも問題だったのは、次のキー／バリューで設定しようとすると、よくわからないエラーが発生することでした。

```py:エラーが発生する設定例
import NetworkManager
import uuid

# EsampleSSIDというSSID（パスワード無し）への接続設定を行う辞書型
example_connection = {
    "connection": {
        "id": "ExampleSSID",
        "type": "802-11-wireless",
    },
    "802-11-wireless": {
        "mode": "infrastructure",
        "ssid": "ExampleSSID",
    },
    "user": {
        "data": {
            # この2つが問題！
            "example-conf": 1,
            "example-desc": "This is example description",
        },
    },
}

# ここでエラーが発生する
NetworkManager.Settings.AddConnection(example_connection)
```

```sh:エラー文
key requires a '.' for a namespace
```

> python-networkmanager/add_connection.py at master · seveas/python-networkmanager
> https://github.com/seveas/python-networkmanager/blob/master/examples/add_connection.py

これがどうしても分からなかったため、NetworkManagerのソースコードを読んでみました。

すると、下記に次のような記述がありました。

```c:libnm-core/nm-setting-user.c
gboolean
nm_setting_user_check_key (const char *key, GError **error)
{
	// ...

	has_dot = FALSE;
	while (TRUE) {
		// ...

		if (ch == '\0') {
			if (!has_dot) {
				g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				                     _("key requires a '.' for a namespace"));
				return FALSE;
			}
			return TRUE;
		}

		if (ch != '.')
			break;

		has_dot = TRUE;

		// ...
	}
	// ...
}
```

> libnm-core/nm-setting-user.c · master · NetworkManager / NetworkManager · GitLab
> https://gitlab.freedesktop.org/NetworkManager/NetworkManager/blob/master/libnm-core/nm-setting-user.c#L139-143

上記は、userセクションにおけるキー判定において、 `.` 文字が存在しなかった場合は強制的に設定できない（ `FALSE` を返す）という仕組みです。

```py:エラーを解決した設定例
import NetworkManager
import uuid

# EsampleSSIDというSSID（パスワード無し）への接続設定を行う辞書型
example_connection = {
    "connection": {
        "id": "ExampleSSID",
        "type": "802-11-wireless",
    },
    "802-11-wireless": {
        "mode": "infrastructure",
        "ssid": "ExampleSSID",
    },
    "user": {
        "data": {
            # s/example-(conf|desc)/example.$1/g
            "example.conf": 1,
            "example.desc": "This is example description",
        },
    },
}

# エラーは発生しない
NetworkManager.Settings.AddConnection(example_connection)
```



# おわりに

NetworkManagerにおけるドキュメントは豊富であるものの、各セクションにおけるキーの扱いや型の扱いは独特です。
しかし、読み方さえ理解できれば、一般的な知識で利用できることがわかります。

NetworkManagerで設定ごとにカスタム属性を追加できることで、様々な拡張が可能です。
利用したい方はぜひ参考にしてみてください。