NetworkManager の仕組み

Linuxその2 Advent Calendar 2020 の 9 日目です。 この記事は、普段ググりながら適当に使っている NetworkManager について、どういう仕組みなんだっけ?を調べなおすものです。

検証環境

CentOS 8 を利用する。

# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)
# rpm -q NetworkManager
NetworkManager-1.22.8-5.el8_2.x86_64

NetworkManager とは

NetoworkManager はネットワーク設定をしてくれるデーモン。

# systemctl status NetworkManager
● NetworkManager.service - Network Manager
   Loaded: loaded (/usr/lib/systemd/system/NetworkManager.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2020-12-01 09:50:09 JST; 3 days ago
     Docs: man:NetworkManager(8)
 Main PID: 949 (NetworkManager)
    Tasks: 4 (limit: 49090)
   Memory: 10.5M
   CGroup: /system.slice/NetworkManager.service
           mq949 /usr/sbin/NetworkManager --no-daemon

nmcli は DBus(プロセス間通信の仕組み) 経由で NetworkManager と通信し、ネットワークを操作する CLI ツール。 NetworkManager を利用したツールは nmcli のほかに、 nmtui や GUI などがある。

nmcli 利用時に、 DBus 経由でネットワーク情報を取ってくる様子をモニタしてみると、インタフェース情報などがやり取りされている様子がわかる。

// eth0 の情報を表示する
terminal A ]# nmcli connection show eth0

// NetworkManager から DBus 経由でネットワーク情報を取ってくる様子をモニタする
terminal B ]# dbus-monitor --system
...
method call time=1607402291.256118 sender=:1.8 -> destination=org.freedesktop.DBus serial=1245 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetConnectionUnixUser
   string ":1.115"
method return time=1607402291.256138 sender=org.freedesktop.DBus -> destination=:1.8 serial=158 reply_serial=1245
   uint32 0
method call time=1607402291.256490 sender=:1.115 -> destination=:1.8 serial=12 path=/org/freedesktop/NetworkManager/Settings/1; interface=org.freedesktop.NetworkManager.Settings.Connection; member=GetSettings
method call time=1607402291.256503 sender=:1.115 -> destination=:1.8 serial=13 path=/org/freedesktop/NetworkManager/Settings/2; interface=org.freedesktop.NetworkManager.Settings.Connection; member=GetSettings
method call time=1607402291.256512 sender=:1.115 -> destination=:1.8 serial=14 path=/org/freedesktop/NetworkManager/Settings/3; interface=org.freedesktop.NetworkManager.Settings.Connection; member=GetSettings
method call time=1607402291.256520 sender=:1.8 -> destination=org.freedesktop.DBus serial=1246 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetConnectionUnixProcessID
   string ":1.115"
method return time=1607402291.256534 sender=org.freedesktop.DBus -> destination=:1.8 serial=159 reply_serial=1246
   uint32 25500
method return time=1607402291.256547 sender=:1.8 -> destination=:1.115 serial=1247 reply_serial=12
   array [
      dict entry(
         string "ipv4"
         array [
            dict entry(
               string "address-data"
               variant                   array [
                     array [
                        dict entry(
                           string "address"
                           variant                               string "192.168.11.11"
                        )
                        dict entry(
                           string "prefix"
                           variant                               uint32 24
                        )
                     ]
                  ]
            )
...

設定変更時の動作

NetworkManager によってネットワーク設定をすると、主に/etc/sysconfig/ 配下のファイルが書き変えられる。 (一部の設定項目では /etc/NetworkManager/ が利用されるなどの例外はある)

# nmcli connection modify eth0 ipv4.dns 8.8.8.8

上記コマンド実行前後の /etc/sysconfig/network-scripts/ifcfg-eth0 ファイルの diff を取ると、次のようになっており、ファイルが書き換えられているのがわかる。

19c19
< DNS1=192.168.11.1
---
> DNS1=8.8.8.8

起動時の動作

NetworkManager 起動時は、 /etc/sysconfig/network-scripts/ifcfg-eth0 などの設定ファイルを読み込んでネットワークを設定する。

bpftrace を用いてトレースすると、NetworkManager 起動時に /etc/sysconfig/network-scripts/ifcfg-eth0 などが読み込まれている様子がわかる。

terminal A ]# systemctl restart NetworkManager

// NetworkManager 再起動時に /etc/resolv.conf を読み込んでいることを確認する
terminal B ]# bpftrace -e 'tracepoint:syscalls:sys_enter_openat / str(args->filename) == "/etc/resolv.conf" / { printf("%s\n", comm);  } '
Attaching 1 probe...
NetworkManager
pool
setroubleshootd
rpm

// NetworkManager 再起動時に /etc/sysconfig/network-scripts/ifcfg-eth0 を読み込んでいることを確認する
terminal C ]# bpftrace -e 'tracepoint:syscalls:sys_enter_openat / str(args->filename) == "/etc/sysconfig/network-scripts/ifcfg-eth0" / { printf("%s\n", comm);  } '
Attaching 1 probe...
NetworkManager
11-dhclient

NetworkManager を経由して設定変更した場合はその更新がクライアントに通知されるが、/etc/sysconfig/network-scripts/ifcfg-eth0などを直接編集した場合は、NetworkManager への再読み込み要求が必要になる。この場合、nmcli connection up connection_name を実行すればよい。

iproute2 パッケージ

nmcli と違い ip や ss は、NetworkManager を停止しても失敗しない。そういえば、なんでなんだろう。

# systemctl stop NetworkManager
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0b:06:27 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.11/24 brd 192.168.11.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0b:06:2c brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.12/24 brd 192.168.11.255 scope global dynamic eth1
       valid_lft 162607sec preferred_lft 162607sec
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:0b:06:2d brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.13/24 brd 192.168.11.255 scope global dynamic eth2
       valid_lft 162607sec preferred_lft 162607sec

# ss -t
State         Recv-Q         Send-Q                  Local Address:Port                   Peer Address:Port
ESTAB         0              0                       192.168.11.11:ssh                    192.168.11.5:55389
ESTAB         0              36                      192.168.11.11:ssh                    192.168.11.5:55128
ESTAB         0              0                       192.168.11.11:ssh                    192.168.11.5:50120
ESTAB         0              0                       192.168.11.11:ssh                    192.168.11.5:51278

この理由は、 ip コマンドは NetworkManager を経由してないから。(だから ip コマンドで何かを設定したところで永続化されない。再起動すると元に戻る。)

terminal A ]# ip addr

// 何も表示されない
terminal B ]# dbus-monitor --system 

では ip コマンドはどうしているかというと、 netlink を利用して直接カーネルとやり取りしている(nl_pid=0)。

# strace ip addr 2>&1 | grep "^socket" -A 5
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0
setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0
setsockopt(3, SOL_NETLINK, NETLINK_EXT_ACK, [1], 4) = 0
bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0
getsockname(3, {sa_family=AF_NETLINK, nl_pid=8326, nl_groups=00000000}, [12]) = 0

netlinkファンのためのnlmon を参考に、netlink を流れる通信をキャプチャすると、その様子がわかる。

terminal A ]# modprobe nlmon
terminal A ]# ip link add nlmon0 type nlmon
terminal A ]# ip link set nlmon0 up

terminal B ]# ip link
terminal A ]# tshark -i nlmon0 -V
Frame 1: 1328 bytes on wire (10624 bits), 1328 bytes captured (10624 bits) on interface 0
    Interface id: 0 (nlmon0)
        Interface name: nlmon0
    Encapsulation type: Linux Netlink (158)
    Arrival Time: Dec  8, 2020 20:27:50.798021600 JST
    [Time shift for this packet: 0.000000000 seconds]
    Epoch Time: 1607426870.798021600 seconds
    [Time delta from previous captured frame: 0.000000000 seconds]
    [Time delta from previous displayed frame: 0.000000000 seconds]
    [Time since reference or first frame: 0.000000000 seconds]
    Frame Number: 1
    Frame Length: 1328 bytes (10624 bits)
    Capture Length: 1328 bytes (10624 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: netlink:netlink-route]
Linux netlink (cooked header)
    Link-layer address type: Netlink (824)
    Family: Route (0x0000)
    ...

なお、NetworkManager も DBus 経由でリクエストを受け付けたあとの実際のネットワーク設定には netlink を利用している。

まとめ

まとめると、NetworkManager や ip コマンドは以下のようになる。

f:id:kimulla:20201208215434p:plain

参考

NetworkManager の詳細な利用方法については、ネットワークの設定および管理 Red Hat Enterprise Linux 8 | Red Hat Customer Portal を参照してください。