Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services/presence/psutils.py
blob: 4d6ae3d59866628ee11b7a122b5070a8e41feb27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import dbus, dbus.glib, gobject
import logging


_logger = logging.getLogger('s-p-s.psutils')


def bytes_to_string(bytes):
    """The function converts a  D-BUS byte array provided by dbus to string format.
    
    bytes -- a D-Bus array of bytes. Handle both DBus byte arrays and strings
    
    """
    try:
        # DBus Byte array
        ret = ''.join([chr(item) for item in bytes])
    except TypeError:
        # Python string
        ret = ''.join([str(item) for item in bytes])
    return ret


NM_SERVICE = 'org.freedesktop.NetworkManager'
NM_IFACE = 'org.freedesktop.NetworkManager'
NM_IFACE_DEVICES = 'org.freedesktop.NetworkManager.Devices'
NM_PATH = '/org/freedesktop/NetworkManager'

_ip4am = None

class IP4AddressMonitor(gobject.GObject):
    """This class, and direct buddy IPv4 address access, will go away quite soon"""

    __gsignals__ = {
        'address-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
                               ([gobject.TYPE_PYOBJECT]))
    }

    __gproperties__ = {
        'address' : (str, None, None, None, gobject.PARAM_READABLE)
    }

    def get_instance():
        """Retrieve (or create) the IP4Address monitor singleton instance"""
        global _ip4am
        if not _ip4am:
            _ip4am = IP4AddressMonitor()
        return _ip4am
    get_instance = staticmethod(get_instance)

    def __init__(self):
        gobject.GObject.__init__(self)
        self._nm_present = False
        self._matches = []
        self._addr = None
        self._nm_obj = None

        sys_bus = dbus.SystemBus()
        bus_object = sys_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
        try:
            if bus_object.GetNameOwner(NM_SERVICE, dbus_interface='org.freedesktop.DBus'):
                self._nm_present = True
        except dbus.DBusException:
            pass

        if self._nm_present:
            self._connect_to_nm()
        else:
            addr = self._get_address_fallback()
            self._update_address(addr)

    def do_get_property(self, pspec):
        if pspec.name == "address":
            return self._addr

    def _update_address(self, new_addr):
        if new_addr == "0.0.0.0":
            new_addr = None
        if new_addr == self._addr:
            return

        self._addr = new_addr
        _logger.debug("IP4 address now '%s'" % new_addr)
        self.emit('address-changed', new_addr)

    def _connect_to_nm(self):
        """Connect to NM device state signals to tell when the IPv4 address changes"""
        try:
            sys_bus = dbus.SystemBus()
            proxy = sys_bus.get_object(NM_SERVICE, NM_PATH)
            self._nm_obj = dbus.Interface(proxy, NM_IFACE)
        except dbus.DBusException, err:
            _logger.debug("Error finding NetworkManager: %s" % err)
            self._nm_present = False
            return

        sys_bus = dbus.SystemBus()
        match = sys_bus.add_signal_receiver(self._nm_device_active_cb,
                                            signal_name="DeviceNowActive",
                                            dbus_interface=NM_IFACE)
        self._matches.append(match)

        match = sys_bus.add_signal_receiver(self._nm_device_no_longer_active_cb,
                                            signal_name="DeviceNoLongerActive",
                                            dbus_interface=NM_IFACE,
                                            bus_name=NM_SERVICE)
        self._matches.append(match)

        match = sys_bus.add_signal_receiver(self._nm_state_change_cb,
                                            signal_name="StateChange",
                                            dbus_interface=NM_IFACE,
                                            bus_name=NM_SERVICE)
        self._matches.append(match)

        state = self._nm_obj.state()
        if state == 3: # NM_STATE_CONNECTED
            self._query_devices()

    def _device_properties_cb(self, *props):        
        active = props[4]
        if not active:
            return
        act_stage = props[5]
        # HACK: OLPC NM has an extra stage, so activated == 8 on OLPC
        # but 7 everywhere else
        if act_stage != 8 and act_stage != 7:
            # not activated
            return
        self._update_address(props[6])

    def _device_properties_error_cb(self, err):
        _logger.debug("Error querying device properties: %s" % err)

    def _query_device_properties(self, device):
        sys_bus = dbus.SystemBus()
        proxy = sys_bus.get_object(NM_SERVICE, device)
        dev = dbus.Interface(proxy, NM_IFACE_DEVICES)
        dev.getProperties(reply_handler=self._device_properties_cb,
                          error_handler=self._device_properties_error_cb)

    def _get_devices_cb(self, ops):
        """Query each device's properties"""
        for op in ops:
            self._query_device_properties(op)

    def _get_devices_error_cb(self, err):
        _logger.debug("Error getting NetworkManager devices: %s" % err)

    def _query_devices(self):
        """Query NM for a list of network devices"""
        self._nm_obj.getDevices(reply_handler=self._get_devices_cb,
                                error_handler=self._get_devices_error_cb)

    def _nm_device_active_cb(self, device, ssid=None):
        self._query_device_properties(device)

    def _nm_device_no_longer_active_cb(self, device):
        self._update_address(None)

    def _nm_state_change_cb(self, new_state):
        if new_state == 4: # NM_STATE_DISCONNECTED
            self._update_address(None)

    def handle_name_owner_changed(self, name, old, new):
        """Clear state when NM goes away"""
        if name != NM_SERVICE:
            return
        if (old and len(old)) and (not new and not len(new)):
            # NM went away
            self._nm_present = False
            for match in self._matches:
                match.remove()
            self._matches = []
            self._update_address(None)
        elif (not old and not len(old)) and (new and len(new)):
            # NM started up
            self._nm_present = True
            self._connect_to_nm()

    def _get_iface_address(self, iface):
        import socket
        import fcntl
        import struct
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        fd = s.fileno()
        SIOCGIFADDR = 0x8915
        addr = fcntl.ioctl(fd, SIOCGIFADDR, struct.pack('256s', iface[:15]))[20:24]
        s.close()
        return socket.inet_ntoa(addr)

    def _get_address_fallback(self):
        import commands
        (s, o) = commands.getstatusoutput("/sbin/route -n")
        if s != 0:
            return
        for line in o.split('\n'):
            fields = line.split(" ")
            if fields[0] == "0.0.0.0":
                iface = fields[len(fields) - 1]
                return self._get_iface_address(iface)
        return None