#!/usr/bin/env python from subprocess import call, Popen, PIPE from IPy import IP import sys import subprocess import dbus import string import socket import re import os import fcntl import glib import gobject import dbus.service import dbus.mainloop.glib from ConfigParser import SafeConfigParser import glob import obmc.mapper #MAC address mask for locally administered. MAC_LOCAL_ADMIN_MASK = 0x20000000000 BROADCAST_MAC = 0xFFFFFFFFFFFF DBUS_NAME = 'org.openbmc.NetworkManager' OBJ_NAME = '/org/openbmc/NetworkManager/Interface' INV_INTF_NAME = 'xyz.openbmc_project.Inventory.Item.NetworkInterface' INVENTORY_ROOT = '/xyz/openbmc_project/inventory' MAC_PROPERTY = 'MACAddress' network_providers = { 'networkd' : { 'bus_name' : 'org.freedesktop.network1', 'ip_object_name' : '/org/freedesktop/network1/network/default', 'hw_object_name' : '/org/freedesktop/network1/link/_31', 'ip_if_name' : 'org.freedesktop.network1.Network', 'hw_if_name' : 'org.freedesktop.network1.Link', 'method' : 'org.freedesktop.network1.Network.SetAddr' }, 'NetworkManager' : { 'bus_name' : 'org.freedesktop.NetworkManager', 'ip_object_name' : '/org/freedesktop/NetworkManager', 'hw_object_name' : '/org/freedesktop/NetworkManager', 'ip_if_name' : 'org.freedesktop.NetworkManager', 'hw_if_name' : 'org.freedesktop.NetworkManager', 'method' : 'org.freedesktop.NetworkManager' # FIXME: }, } def getPrefixLen(mask): prefixLen = sum([bin(int(x)).count('1') for x in mask.split('.')]) return prefixLen # Enable / Disable the UseDHCP setting in .network file def modifyNetConfig(confFile, usentp): parser = SafeConfigParser() parser.optionxform = str parser.read(confFile) sections = parser.sections() if "Match" not in sections: raise NameError, "[Match] section not found" interface = parser.get('Match', 'Name') if interface == '': raise NameError, "Invalid interface" if "DHCP" not in sections: parser.add_section("DHCP") if usentp.lower() == "yes": parser.set('DHCP', 'UseNTP', "true") elif usentp.lower() == "no": parser.set('DHCP', 'UseNTP', "false") print "Updating" + confFile + '\n' with open(confFile, 'wb') as configfile: parser.write(configfile) rc = call(["ip", "addr", "flush", interface]) rc = call(["systemctl", "restart", "systemd-networkd.service"]) rc = call(["systemctl", "try-restart", "systemd-timesyncd.service"]) return rc # Get Mac address from the eeprom def get_mac_from_eeprom(): bus = dbus.SystemBus() mapper = obmc.mapper.Mapper(bus) # Get the inventory subtree, limited # to objects that implement NetworkInterface. for path, info in \ mapper.get_subtree( path=INVENTORY_ROOT, interfaces=[INV_INTF_NAME]).iteritems(): # Find a NetworkInterface with 'bmc' in the path. if 'bmc' not in path: continue # Only expecting a single service to implement # NetworkInterface. Get the service connection # from the mapper response conn = info.keys()[0] # Get the inventory object implementing NetworkInterface. obj = bus.get_object(conn, path) # Get the MAC address mproxy = obj.get_dbus_method('Get', dbus.PROPERTIES_IFACE) return mproxy(INV_INTF_NAME, MAC_PROPERTY) class IfAddr (): def __init__ (self, family, scope, flags, prefixlen, addr, gw): self.family = family self.scope = scope self.flags = flags self.prefixlen = prefixlen self.addr = addr self.gw = gw class NetMan (dbus.service.Object): def __init__(self, bus, name): self.bus = bus self.name = name dbus.service.Object.__init__(self,bus,name) def setNetworkProvider(self, provider): self.provider = provider def _isvaliddev(self, device): devices = os.listdir ("/sys/class/net") if not device in devices : return False else: return True def _ishwdev (self, device): f = open ("/sys/class/net/"+device+"/type") type = f.read() return False if (int(type) == 772) else True def _isvalidmask (self, mask): for x in mask.split('.'): try: y = int(x) except: return False if y > 255: return False return mask.count('.') == 3 def validatemac(self, mac): macre = '([a-fA-F0-9]{2}[:|\-]?){6}' if re.compile(macre).search(mac) is None: raise ValueError("Malformed MAC address") # Don't allow Broadcast or global unique mac int_mac = int(mac.replace(":", ""), 16) if not (int_mac ^ BROADCAST_MAC): raise ValueError("Given Mac is BroadCast Mac Address") if not int_mac & MAC_LOCAL_ADMIN_MASK: eep_mac = get_mac_from_eeprom() if eep_mac: int_eep_mac = int(eep_mac, 16) if int_eep_mac != int_mac: raise ValueError("Given MAC address is neither a local Admin type \ nor is same as in eeprom") def _isvalidipv4(self, ipstr, netmask): ip_parts = ipstr.split(".") if len(ip_parts) != 4: return "Malformed" first, second, third, fourth = [int(part) for part in ip_parts] if first == 0 and second == 0 and third == 0 and fourth == 0: return "Invalid" # "this" network disallowed if first == 169 and second == 254: return "Link Local" if first >= 224: return "Invalid" # class D multicast and class E disallowed if first == 192 and second == 88 and third == 99: return "Invalid" # ipv6 relay # check validity against netmask if netmask != '0': ip_bin = (first << 24) + (second << 16) + (third << 8) + fourth mask_parts = netmask.split(".") if len(mask_parts) == 4: # long form netmask mask_bin = (int(mask_parts[0]) << 24) + (int(mask_parts[1]) << 16) + (int(mask_parts[2]) << 8) + int(mask_parts[3]) elif netmask.count(".") == 0: # short form netmask mask_bin = 0xffffffff ^ (1 << 32 - int(netmask)) - 1 else: return "Malformed" # bad netmask if ip_bin & ~mask_bin == 0: return "Invalid" # disallowed by this netmask if ip_bin | mask_bin == 0xFFFFFFFF: return "Invalid" # disallowed by this netmask return "Valid" def _isvalidip(self, ipaddr, netmask = '0'): try: ip = IP(ipaddr) except ValueError: return "Malformed" ipstr = ip.strNormal(0) ipstr_masked = ip.strNormal(2) if ipstr_masked.count("/") != 0 and netmask == '0': netmask = ipstr_masked.split("/")[1] if ip.version() == 4: # additional checks for ipv4 return self._isvalidipv4(ipstr, netmask) # TODO: check ipv6 openbmc/openbmc#496 return "Valid" def _getAddr (self, target, device): netprov = network_providers [self.provider] bus_name = netprov ['bus_name'] if (target == "ip"): ipaddr = "" defgw = "" prefixlen = "0" proc = subprocess.Popen(["ip", "addr", "show", "dev", device], stdout=PIPE) procout = proc.communicate() if procout: ipout = procout[0].splitlines()[2].strip() ipaddr,prefixlen = ipout.split ()[1].split("/") proc = subprocess.Popen(["ip", "route", "show", "dev", device, "default", "0.0.0.0/0"], stdout=PIPE) procout = proc.communicate() if procout[0]: ipout = procout[0].splitlines()[0].strip() defgw = ipout.split ()[2] return 2, int(prefixlen), ipaddr, defgw if (target == "mac"): proc = subprocess.Popen(["ip", "link", "show", "dev", device], stdout=PIPE) ipout = proc.communicate()[0].splitlines()[1].strip() mac = ipout.split ()[1] return mac def _checkNetworkForRequiredFields(self, device): parser = SafeConfigParser() parser.optionxform = str parser.read("/etc/systemd/network/00-bmc-" + device + ".network") sections = parser.sections() if "Match" not in sections: parser.add_section("Match") parser.set("Match", "Name", device) if "Network" not in sections: parser.add_section("Network") if "Link" not in sections: parser.add_section("Link") mac = self._getAddr("mac", device) try: self.validatemac(mac) parser.set("Link", MAC_PROPERTY, mac) except ValueError as e: print("System MAC Address invalid:" + e) return parser def _upDownNetworkService(self, device, mac=None): print "Restarting networkd service..." subprocess.check_call(["ip", "link", "set", "dev", device, "down"]) if mac is not None: subprocess.check_call(["fw_setenv", "ethaddr", mac]) subprocess.check_call( ["ip", "link", "set", "dev", device, "address", mac]) subprocess.check_call( ["systemctl", "restart", "systemd-networkd.service"]) return 0 @dbus.service.method(DBUS_NAME, "sas", "x") def SetNtpServer (self, device, ntpservers): if not self._isvaliddev (device) : raise ValueError, "Invalid Device" # Convert the array into space separated value string ntp_ip = " ".join(ntpservers) if not ntp_ip : raise ValueError, "Invalid Data" confFile = "/etc/systemd/network/00-bmc-" + device + ".network" parser = SafeConfigParser() parser.optionxform = str parser.read(confFile) sections = parser.sections() if "Match" not in sections: raise NameError, "[Match] section not found" interface = parser.get('Match', 'Name') if interface != device: raise ValueError, "Device [" + device + "] Not Configured" if "Network" not in sections: raise NameError, "[Network] section not found" parser.set('Network', 'NTP', ntp_ip) print "Updating " + confFile + '\n' with open(confFile, 'wb') as configfile: parser.write(configfile) rc = call(["ip", "addr", "flush", device]) rc = call(["systemctl", "restart", "systemd-networkd.service"]) rc = call(["systemctl", "try-restart", "systemd-timesyncd.service"]) return rc @dbus.service.method(DBUS_NAME, "s", "x") def UpdateUseNtpField (self, usentp): filelist = glob.glob("/etc/systemd/network/*.network") for configfile in filelist: modifyNetConfig(configfile,usentp) return 0 @dbus.service.method(DBUS_NAME, "s", "x") def EnableDHCP(self, device): if not self._isvaliddev(device): raise ValueError("Invalid Device") parser = self._checkNetworkForRequiredFields(device) parser.set("Network", "DHCP", "yes") if "DHCP" not in parser.sections(): parser.add_section("DHCP") parser.set("DHCP", "ClientIdentifier", "mac") # Write to config file with open("/etc/systemd/network/00-bmc-" + device + ".network", 'w') \ as configfile: parser.write(configfile) return (subprocess.check_call( ["systemctl", "restart", "systemd-networkd.service"])) @dbus.service.method(DBUS_NAME, "ssss", "x") def SetAddress4(self, device, ipaddr, netmask, gateway): if not self._isvaliddev(device): raise ValueError, "Invalid Device" if not self._isvalidmask(netmask): raise ValueError, "Invalid Mask" prefixLen = getPrefixLen(netmask) if prefixLen == 0: raise ValueError, "Invalid Mask" valid = self._isvalidip(ipaddr, netmask) if valid != "Valid": raise ValueError, valid + " IP Address" valid = self._isvalidip(gateway) if valid != "Valid": raise ValueError, valid + " IP Address" parser = self._checkNetworkForRequiredFields(device) parser.set("Network", "DHCP", "no") parser.set("Network", "Address", '{}/{}'.format(ipaddr, prefixLen)) parser.set("Network", "Gateway", gateway) with open("/etc/systemd/network/00-bmc-" + device + ".network", 'w') \ as configfile: parser.write(configfile) return (subprocess.check_call( ["systemctl", "restart", "systemd-networkd.service"])) @dbus.service.method(DBUS_NAME, "s", "s") def GetAddressType (self, device): if not self._isvaliddev (device) : raise ValueError, "Invalid Device" confFile = "/etc/systemd/network/00-bmc-" + device + ".network" if not os.path.exists(confFile): print "Config file (%s) not found !" % confFile netprov = network_providers [self.provider] bus_name = netprov ['bus_name'] obj_name = netprov ['ip_object_name'] o = self.bus.get_object(bus_name, obj_name, introspect=False) i = dbus.Interface(o, 'org.freedesktop.DBus.Properties') f = i.Get (netprov ['ip_if_name'], "SourcePath") print "Using default networkd config file (%s)" % f confFile = f parser = self._checkNetworkForRequiredFields(device) if parser.has_option("Network", "DHCP") is True: mode = parser.get("Network", "DHCP") else: return "Unknown" setting = { 'yes': 'DHCP', 'true': 'DHCP', 'no': 'STATIC' } setting.get(mode.lower(), "Unknown") return setting #family, prefixlen, ip, defgw @dbus.service.method(DBUS_NAME, "s", "iyss") def GetAddress4 (self, device): if not self._isvaliddev (device) : raise ValueError, "Invalid Device" return self._getAddr ("ip", device) @dbus.service.method(DBUS_NAME, "s", "s") def GetHwAddress (self, device): if not self._isvaliddev (device) : raise ValueError, "Invalid Device" return self._getAddr ("mac", device) @dbus.service.method(DBUS_NAME, "ss", "i") def SetHwAddress (self, device, mac): if not self._isvaliddev (device) : raise ValueError, "Invalid Device" if not self._ishwdev (device) : raise ValueError, "Not a Hardware Device" self.validatemac(mac) parser = self._checkNetworkForRequiredFields(device) parser.set("Link", MAC_PROPERTY, mac) with open("/etc/systemd/network/00-bmc-" + device + ".network", 'w') as configfile: parser.write(configfile) rc = subprocess.call(["fw_setenv", "ethaddr", mac]) print("Restarting networkd service...") rc = call(["ip", "link", "set", "dev", device, "down"]) rc = call(["ip", "link", "set", "dev", device, "address", mac]) rc = call(["ip", "link", "set", "dev", device, "up"]) rc = call(["systemctl", "restart", "systemd-networkd.service"]) return rc #string of nameservers @dbus.service.method(DBUS_NAME,"s", "s") def SetNameServers (self, nameservers): dns_entry = nameservers.split() fail_msg = '' dhcp_auto = False file_opened = False if len(dns_entry) > 0: for dns in dns_entry: valid = self._isvalidip (dns) if valid != "Valid": if dns == "DHCP_AUTO=": #This DNS is supplied by DHCP. dhcp_auto = True else: print valid + " DNS Address [" + dns + "]" fail_msg = fail_msg + '[' + dns + ']' else: #Only over write on a first valid input if file_opened == False: resolv_conf = open("/etc/resolv.conf",'w') file_opened = True if dhcp_auto == True: resolv_conf.write("### Generated automatically via DHCP ###\n") else: resolv_conf.write("### Generated manually via dbus settings ###\n") dns_ip = 'nameserver ' + dns + '\n' resolv_conf.write(dns_ip) if file_opened == True: resolv_conf.close() else: raise ValueError, "Invalid DNS entry" if len(fail_msg) > 0: return 'Failures encountered processing' + fail_msg else: return "DNS entries updated Successfully" @dbus.service.method(DBUS_NAME, "s", "x") def SetHostname(self, hostname): subprocess.check_call(["hostnamectl", "set-hostname", hostname]) subprocess.check_call( ["systemctl", "restart", "systemd-networkd.service"]) return 0 @dbus.service.method(DBUS_NAME, "", "s") def GetHostname(self): value = subprocess.check_output("hostname") return value.rstrip() @dbus.service.method(DBUS_NAME, "ss", "x") def SetGateway(self, device, gateway): if not self._isvaliddev(device): raise ValueError("Invalid Device") valid = self._isvalidip(gateway) if valid != "Valid": raise ValueError(valid + " IP Address") parser = self._checkNetworkForRequiredFields(device) if "no" not in parser.get("Network", "DHCP"): raise EnvironmentError("DHCP is on") parser.set("Network", "Gateway", gateway) with open("/etc/systemd/network/00-bmc-" + device + ".network", 'w') \ as configfile: parser.write(configfile) return (subprocess.check_call( ["systemctl", "restart", "systemd-networkd.service"])) @dbus.service.method(DBUS_NAME, "s", "s") def GetGateway(self, device): if not self._isvaliddev(device): raise ValueError("Invalid Device") return self._getAddr("ip", device)[3] def main(): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() name = dbus.service.BusName(DBUS_NAME, bus) obj = NetMan (bus, OBJ_NAME) obj.setNetworkProvider ("networkd") mainloop = gobject.MainLoop() print("Started") mainloop.run() if __name__ == '__main__': sys.exit(main())