glances_ip.py 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""IP plugin."""

22 23 24 25 26
import Queue
import threading
from json import load

from glances.compat import iterkeys, urlopen, URLError
A
Alessio Sergi 已提交
27
from glances.globals import BSD
28
from glances.logger import logger
29
from glances.timer import Timer
30 31
from glances.plugins.glances_plugin import GlancesPlugin

32
# XXX *BSDs: Segmentation fault (core dumped)
33
# -- https://bitbucket.org/al45tair/netifaces/issues/15
A
Alessio Sergi 已提交
34
if not BSD:
35 36 37 38 39 40 41 42
    try:
        import netifaces
        netifaces_tag = True
    except ImportError:
        netifaces_tag = False
else:
    netifaces_tag = False

43 44 45 46 47 48 49 50 51 52
# List of online services to retreive public IP address
# List of tuple (url, json, key)
# - url: URL of the Web site
# - json: service return a JSON (True) or string (False)
# - key: key of the IP addresse in the JSON structure
urls = [('http://ip.42.pl/raw', False, None),
        ('http://httpbin.org/ip', True, 'origin'),
        ('http://jsonip.com', True, 'ip'),
        ('https://api.ipify.org/?format=json', True, 'ip')]

53 54 55

class Plugin(GlancesPlugin):

A
PEP 257  
Alessio Sergi 已提交
56
    """Glances IP Plugin.
57 58 59 60 61 62

    stats is a dict
    """

    def __init__(self, args=None):
        """Init the plugin."""
A
Alessio Sergi 已提交
63
        super(Plugin, self).__init__(args=args)
64 65 66 67

        # We want to display the stat in the curse interface
        self.display_curse = True

68 69 70
        # Get the public IP address once
        self.public_address = PublicIpAddress().get()

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
        # Init the stats
        self.reset()

    def reset(self):
        """Reset/init the stats."""
        self.stats = {}

    @GlancesPlugin._log_result_decorator
    def update(self):
        """Update IP stats using the input method.

        Stats is dict
        """
        # Reset stats
        self.reset()

87
        if self.input_method == 'local' and netifaces_tag:
88 89 90
            # Update stats using the netifaces lib
            try:
                default_gw = netifaces.gateways()['default'][netifaces.AF_INET]
N
nicolargo 已提交
91
            except (KeyError, AttributeError) as e:
92
                logger.debug("Cannot grab the default gateway ({0})".format(e))
93 94 95 96 97 98
            else:
                try:
                    self.stats['address'] = netifaces.ifaddresses(default_gw[1])[netifaces.AF_INET][0]['addr']
                    self.stats['mask'] = netifaces.ifaddresses(default_gw[1])[netifaces.AF_INET][0]['netmask']
                    self.stats['mask_cidr'] = self.ip_to_cidr(self.stats['mask'])
                    self.stats['gateway'] = netifaces.gateways()['default'][netifaces.AF_INET][0]
99 100
                    # !!! SHOULD be done once, not on each refresh
                    self.stats['public_address'] = self.public_address
N
nicolargo 已提交
101
                except (KeyError, AttributeError) as e:
102
                    logger.debug("Cannot grab IP information: {0}".format(e))
103
        elif self.input_method == 'snmp':
104 105 106 107 108 109 110 111 112
            # Not implemented yet
            pass

        # Update the view
        self.update_views()

        return self.stats

    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
113
        """Update stats views."""
114
        # Call the father's method
A
Alessio Sergi 已提交
115
        super(Plugin, self).update_views()
116 117 118

        # Add specifics informations
        # Optional
A
Alessio Sergi 已提交
119
        for key in iterkeys(self.stats):
120 121 122 123 124 125 126 127 128 129 130 131
            self.views[key]['optional'] = True

    def msg_curse(self, args=None):
        """Return the dict to display in the curse interface."""
        # Init the return message
        ret = []

        # Only process if stats exist and display plugin enable...
        if not self.stats or args.disable_ip:
            return ret

        # Build the string message
A
Alessio Sergi 已提交
132
        msg = ' - '
N
nicolargo 已提交
133
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
134
        msg = 'IP '
135
        ret.append(self.curse_add_line(msg, 'TITLE'))
N
nicolargo 已提交
136
        msg = '{0:}/{1}'.format(self.stats['address'], self.stats['mask_cidr'])
137
        ret.append(self.curse_add_line(msg))
138 139 140 141 142
        if self.stats['public_address'] is not None:
            msg = ' Pub '
            ret.append(self.curse_add_line(msg, 'TITLE'))
            msg = '{0:}'.format(self.stats['public_address'])
            ret.append(self.curse_add_line(msg))
143 144 145 146 147

        return ret

    @staticmethod
    def ip_to_cidr(ip):
A
PEP 257  
Alessio Sergi 已提交
148 149 150 151
        """Convert IP address to CIDR.

        Example: '255.255.255.0' will return 24
        """
152
        return sum([int(x) << 8 for x in ip.split('.')]) // 8128
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


class PublicIpAddress(object):
    """Get public IP address from online services"""

    def __init__(self, timeout=2):
        self.timeout = timeout

    def get(self):
        """Get the first public IP address returned by one of the online services"""
        q = Queue.Queue()

        for u, j, k in urls:
            t = threading.Thread(target=self._get_ip_public, args=(q, u, j, k))
            t.daemon = True
            t.start()

        t = Timer(self.timeout)
        ip = None
        while not t.finished() and ip is None:
            if q.qsize() > 0:
                ip = q.get()

        return ip

    def _get_ip_public(self, queue, url, json=False, key=None):
        """Request the url service and put the result in the queue"""
        try:
            u = urlopen(url, timeout=self.timeout)
        except URLError:
            queue.put(None)
        else:
            # Request depend on service
            if not json:
                queue.put(u.read())
            else:
                queue.put(load(u)[key])