from itertools import zip_longest
import subprocess
import netifaces
from i3pystatus import IntervalModule
# Reminder: if we raise minimum Python version to 3.3, use ipaddress module
def count_bits(integer):
bits = 0
while (integer):
integer &= integer - 1
bits += 1
return bits
def v6_to_int(v6):
return int(v6.replace(":", ""), 16)
def prefix6(mask):
return count_bits(v6_to_int(mask))
def cidr6(addr, mask):
return "{addr}/{bits}".format(addr=addr, bits=prefix6(mask))
def v4_to_int(v4):
sum = 0
mul = 1
for part in reversed(v4.split(".")):
sum += int(part) * mul
mul *= 2 ** 8
return sum
def prefix4(mask):
return count_bits(v4_to_int(mask))
def cidr4(addr, mask):
return "{addr}/{bits}".format(addr=addr, bits=prefix4(mask))
def get_bonded_slaves():
try:
with open("/sys/class/net/bonding_masters") as f:
masters = f.read().split()
except FileNotFoundError:
return {}
slaves = {}
for master in masters:
with open("/sys/class/net/{}/bonding/slaves".format(master)) as f:
for slave in f.read().split():
slaves[slave] = master
return slaves
def sysfs_interface_up(interface, unknown_up=False):
try:
with open("/sys/class/net/{}/operstate".format(interface)) as f:
status = f.read().strip()
except FileNotFoundError:
raise RuntimeError("Unknown interface {iface}!".format(iface=interface))
return status == "up" or unknown_up and status == "unknown"
[docs]class Network(IntervalModule):
"""
Display network information about a interface.
Requires the PyPI package `netifaces`.
.. rubric:: Available formatters
* `{interface}` — same as setting
* `{name}` — same as setting
* `{v4}` — IPv4 address
* `{v4mask}` — subnet mask
* `{v4cidr}` — IPv4 address in cidr notation (i.e. 192.168.2.204/24)
* `{v6}` — IPv6 address
* `{v6mask}` — subnet mask
* `{v6cidr}` — IPv6 address in cidr notation
* `{mac}` — MAC of interface
Not available addresses (i.e. no IPv6 connectivity) are replaced with empty strings.
"""
settings = (
("interface", "Interface to obtain information for"),
"format_up", "color_up",
"format_down", "color_down",
("detached_down", "If the interface doesn't exist, display it as if it were down"),
("unknown_up", "If the interface is in unknown state, display it as if it were up"),
"name",
)
name = interface = "eth0"
format_up = "{interface}: {v4}"
format_down = "{interface}"
color_up = "#00FF00"
color_down = "#FF0000"
detached_down = True
unknown_up = False
def init(self):
if self.interface not in netifaces.interfaces() and not self.detached_down:
raise RuntimeError(
"Unknown interface {iface}!".format(iface=self.interface))
def collect(self):
if self.interface not in netifaces.interfaces() and self.detached_down:
self.format = self.format_down
color = self.color_down
return self.color_down, self.format_down, {"interface": self.interface, "name": self.name}, False
info = netifaces.ifaddresses(self.interface)
slaves = get_bonded_slaves()
try:
master = slaves[self.interface]
except KeyError:
pass
else:
if sysfs_interface_up(self.interface, self.unknown_up):
master_info = netifaces.ifaddresses(master)
for af in (netifaces.AF_INET, netifaces.AF_INET6):
try:
info[af] = master_info[af]
except KeyError:
pass
up = sysfs_interface_up(self.interface, self.unknown_up)
fdict = dict(
zip_longest(["v4", "v4mask", "v4cidr", "v6", "v6mask", "v6cidr"], [], fillvalue=""))
try:
mac = info[netifaces.AF_PACKET][0]["addr"]
except KeyError:
mac = "NONE"
fdict.update({
"interface": self.interface,
"name": self.name,
"mac": mac,
})
if up:
format = self.format_up
color = self.color_up
if netifaces.AF_INET in info:
v4 = info[netifaces.AF_INET][0]
fdict["v4"] = v4["addr"]
fdict["v4mask"] = v4["netmask"]
fdict["v4cidr"] = cidr4(v4["addr"], v4["netmask"])
if netifaces.AF_INET6 in info:
for v6 in info[netifaces.AF_INET6]:
fdict["v6"] = v6["addr"]
fdict["v6mask"] = v6["netmask"]
fdict["v6cidr"] = cidr6(v6["addr"], v6["netmask"])
if not v6["addr"].startswith("fe80::"): # prefer non link-local addresses
break
else:
format = self.format_down
color = self.color_down
return color, format, fdict, up
def run(self):
color, format, fdict, up = self.collect()
self.output = {
"full_text": format.format(**fdict),
"color": color,
"instance": self.interface
}
def on_leftclick(self):
subprocess.Popen(["nm-connection-editor"])