最近更新: 2010-08-04

監聽 NetworkManager 的設備異動訊號,執行指定行為(設定 eth0~1)

我的 ThinkPad 安裝 Ubuntu 桌面系統,配合使用需求,我的有線網路設備除了啟用 DHCP 之外,還額外設定了虛擬介面連接內部網路。概略如下列所示:

eth0    inet addr:(DHCP)

eth0:1  inet addr:10.1.2.3

但是,因為我的網路設備是由 NetworkManager 所管理,基於 NetworkManager 以及筆記型電腦的操作特性,每當我切換桌面使用者亦或是筆電進入睡眠狀態,都會導致網路設備重置,使得 eth0:1 的設置消失。為此,我寫了一個小程式,透過 NetworkManager 提供的 D-Bus 服務,監聽 eth0 主設備的狀態,每當 eth0 被啟用亦或重新啟用,都能立即執行 ifconfig 設定 eth0:1 。

附帶一提,希望有人可以教我如何在 NetworkManager 中設定 eth0:1 ,感激不盡。

考慮到系統的普及性,這個小程式 (eth0.1.up) 是用 Python 寫的。我個人認為,就語法易學度與軟體普及度來看,在 Linux 桌面環境上,python 足以取代 perl 做為撰寫 script 程式的首選了。在 Debian/Ubuntu 桌面環境上,執行這個小程式的相關套件,都是預設安裝項目。通常不必再額外安裝套件便可使用。

#!/usr/bin/python
### BEGIN INIT INFO
# Provides: eth0.1.up
# Required-Start: network-manager
# Required-Stop:
# Default-Start:
# Default-Stop:
# Short-Description: Raise eth0:1 network interface.
### END INIT INFO
# usage:
#  -foreground (or --foreground): run in foreground

LAN_SETTINGS = {
    'eth0': {
        'address': "10.1.2.3",
        'netmask': "255.255.255.0"
    }
}

ETH_V1_UP="/sbin/ifconfig %s:1 inet %s netmask %s up"

import os,sys
import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop

if len(sys.argv) > 1 and sys.argv[1].endswith("-foreground"):
    print "Foreground mode running..."
else:
    #run this program in background.

    try:
        pid = os.fork()
    except OSError:
        sys.exit(1)
    if pid > 0:
        sys.exit(0)

NetworkManagerServiceName = "org.freedesktop.NetworkManager"
NetworkManagerObjectPath = "/org/freedesktop/NetworkManager"
NetworkManagerInterface = "org.freedesktop.NetworkManager"
NetworkManagerDeviceInterface = "org.freedesktop.NetworkManager.Device"
DBusPropertiesInterface="org.freedesktop.DBus.Properties"

NM_DEVICE_STATE_ACTIVATED=8

class ActivatedHandler(object):
    def __init__(self, device_instance):
        self.device_instance = device_instance
        props = device_instance.GetAll(NetworkManagerDeviceInterface,
            dbus_interface=DBusPropertiesInterface)
        self.name = props['Interface']
        self.device_instance.connect_to_signal("StateChanged",
            self.handler,
            dbus_interface=NetworkManagerDeviceInterface)

    def handler(self, new_state, old_state, reason):
        if new_state == NM_DEVICE_STATE_ACTIVATED:
            #print "%s activated" % self.name

            os.system(ETH_V1_UP % (self.name,
                LAN_SETTINGS[self.name]['address'],
                LAN_SETTINGS[self.name]['netmask']))

DBusGMainLoop(set_as_default=True)
loop = gobject.MainLoop()

bus = dbus.SystemBus()
try:
    nm_instance = bus.get_object(NetworkManagerServiceName, NetworkManagerObjectPath)
except dbus.DBusException:
    print "connect to NetworkManager error."
    sys.exit(1)

handlers = {}

devices = nm_instance.GetDevices(dbus_interface=NetworkManagerInterface)
for device in devices:
    device_instance = bus.get_object(NetworkManagerServiceName, device)
    props = device_instance.GetAll(NetworkManagerDeviceInterface,
        dbus_interface=DBusPropertiesInterface)
    device_name = props['Interface'] # eg. "eth0"

    if device_name not in LAN_SETTINGS:
        continue
    if props['State'] == NM_DEVICE_STATE_ACTIVATED:
        #print "%s activated" % device_name

        os.system(ETH_V1_UP % (device_name,
            LAN_SETTINGS[device_name]['address'],
            LAN_SETTINGS[device_name]['netmask']))
    handlers[device_name] = ActivatedHandler(device_instance)

devices = False
#print "Working..."

loop.run()

NetworkManager 的 D-Bus 服務,稱得上是目前 D-Bus 服務提供者的經典設計。它可以為每個連接設備實體化一個專責的 D-Bus 服務個體,因此我們首先需要透過主服務介面的 GetDevices() 方法查詢目前活動中的 D-Bus 服務個體的路徑(object path)。再透過此路徑,去接收 eth0 設備的狀態異動訊號(StateChanged signal)。每當狀態改變為 NM_DEVICE_STATE_ACTIVATED (設備已啟用)之後,便調用外部程式 ifconfig 設定 eth0:1 。

儲存上列的 Python 程式碼。以本文為例,命令為 eth0.1.up ,並賦予它可執行權限。接著找個目錄放置,例如 /etc/init.d 。

在此提供兩種方法,令它可在系統啟動後自動執行。

set in rc.local

最簡單的方式,就是手動編譯 /etc/rc.local ,把它的執行指令加到 /etc/rc.local 內。例如:

# rc.local


# Enable eth0:1 virtual network interface.

/etc/init.d/eth0.1.up

exit 0

記得把指令加到 rc.local 最後一行的 exit 0 之前。

set by update-rc.d

第二種作法,就是使用指令 update-rc.d ,將 eth0.1.up 加到 rc?.d 的啟動清單中。通常自定的指令稿都給予第99號啟動順序。如下例:


$ sudo update-rc.d eth0.1.up defaults 99
 Adding system startup for /etc/init.d/eth0.1.up ...
   /etc/rc0.d/K99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc1.d/K99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc6.d/K99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc2.d/S99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc3.d/S99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc4.d/S99eth0.1.up -> ../init.d/eth0.1.up
   /etc/rc5.d/S99eth0.1.up -> ../init.d/eth0.1.up

注意,使用 update-rc.d 時,指令稿必須儲放於 /etc/init.d 目錄內 ,否則 update-rc.d 找不到。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/13345505.html

樂多舊回應
metavige@gmail.com(metavige) (#comment-21038631)
Thu, 05 Aug 2010 13:26:48 +0800
可以做 softlink 到 /etc/rcS.d 中
只要符合命名規則
1. S 開頭
2. 接下來用數字代表啟動順序
3. 把 script 放在 /etc/init.d

舉例:
1. 將 eth0.1.up 複製到 /etc/init.d
2. 到 /etc/rcS.d 目錄下,執行 ln -s /etc/init.d/eth0.1.up S99eth0.1.up
(99 是表示順序)

其他可參考 /etc/rcS.d/README 的說明
未留名 (#comment-21039247)
Thu, 05 Aug 2010 18:31:00 +0800
metavige 說的內容,就是 update-rc.d 這個 script 會幫我們做的事。用 update-rc.d 比較省事。
metavige@gmail.com(metavige) (#comment-21041387)
Fri, 06 Aug 2010 13:21:29 +0800
這倒是讓我學到了一個新的指令~ ^^