def _createNewPrefix(self, excludeList): item = None while True: item = ("192.168.%d.0" % (random.randint(0, 255)), "255.255.255.0") if WrtUtil.prefixConflictWithPrefixList( item, self.defaultExcludePrefixList): continue if WrtUtil.prefixConflictWithPrefixList(item, excludeList): continue break assert item is not None return item
def refresh_host(self, source_id, ip_data_dict): fn = os.path.join(self.hostsDir, source_id) itemDict = WrtUtil.dnsmasqHostFileToDict(fn) itemDict2 = dict() for ip, data in ip_data_dict.items(): if "hostname" in data: itemDict2[ip] = data["hostname"] if itemDict != itemDict2: WrtUtil.dictToDnsmasqHostFile(itemDict2, fn) self.dnsmasqProc.send_signal(signal.SIGHUP)
def remove_host(self, source_id, ip_list): fn = os.path.join(self.hostsDir, source_id) itemDict = WrtUtil.dnsmasqHostFileToOrderedDict(fn) bChanged = False for ip in ip_list: if ip in itemDict: del itemDict[ip] bChanged = True if bChanged: WrtUtil.dictToDnsmasqHostFile(itemDict, fn) self.dnsmasqProc.send_signal(signal.SIGHUP)
def setExcludePrefixList(self, key, prefixList): """Returns True means conflict is found and solved, reboot needed""" ret = False # get conflict items idxList = [] for i in range(0, len(self.prefixList)): if WrtUtil.prefixConflictWithPrefixList(self.prefixList[i], prefixList): idxList.append(i) if self.prefixList[i][2]: ret = True # program restart needed break # get a reference list for create new prefix refList = [] for i in range(0, len(self.prefixList)): if i not in idxList: refList.append(self.prefixList[i]) # create new prefix for conflict items for i in idxList: pip, pmask = self._createNewPrefix(refList + prefixList) self.prefixList[i] = (pip, pmask, False) self.excludePrefixDict[key] = prefixList self._save() return ret
def __init__(self, param): self.param = param self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__) self.wanConnPluginApi = None self.wanConnPlugin = None self.ifconfigDict = dict() # dict<ifname,ifconfig> try: cfgfile = os.path.join(self.param.etcDir, "wan-connection.json") if os.path.exists(cfgfile): cfgObj = WrtUtil.loadJsonEtcCfg(cfgfile) self.wanConnPluginApi = WanConnectionPluginApi(self, cfgObj["plugin"]) self.wanConnPlugin = self.param.pluginHub.getPlugin("wconn", cfgObj["plugin"]) self.wanConnPlugin.start(cfgObj, self.wanConnPluginApi) self.logger.info("Internet connection activated, plugin: %s." % (cfgObj["plugin"])) else: self.logger.info("No internet connection configured.") except BaseException: if self.wanConnPlugin is not None: self.wanConnPlugin.stop() self.wanConnPlugin = None self.logger.info("Internet connection deactivated.") if self.wanConnPluginApi is not None: self.wanConnPluginApi.dispose() self.wanConnPluginApi = None raise
def __init__(self, param): self.param = param self.cfgFile = os.path.join(self.param.tmpDir, "l2-dnsmasq.conf") self.pidFile = os.path.join(self.param.tmpDir, "l2-dnsmasq.pid") self.hostsDir = os.path.join(self.param.tmpDir, "l2-dnsmasq.hosts.d") self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__) self.wanServDict = dict() # dict<name,json-object> self.tfacGroupDict = dict() # dict<name, priority> self.routeFullDict = _NamePriorityKeyValueDict() self.routeDict = dict() # dict<prefix, data> self.gatewayDict = dict() # dict<name, set<interface>> self.domainNameserverFullDict = _NamePriorityKeyValueDict() self.domainNameserverDict = dict() self.domainIpFullDict = _NamePriorityKeyValueDict() self.routeRefreshInterval = 10 # 10 seconds self.routeRefreshTimer = GObject.timeout_add_seconds( self.routeRefreshInterval, self._routeRefreshTimerCallback) self.dnsPort = WrtUtil.getFreeSocketPort("tcp") self.dnsmasqProc = None try: self._runDnsmasq() self.logger.info("Level 2 nameserver started.") except BaseException: self._dispose() raise
def on_wan_conn_up(self): # set exclude prefix and restart if neccessary wanPrefixList = [] for ifc in self.ifconfigDict.values(): wanPrefixList.append(WrtUtil.ipMaskToPrefix(ifc["prefix"].split("/")[0], ifc["prefix"].split("/")[1])) if self.param.prefixPool.setExcludePrefixList("wan", wanPrefixList): os.kill(os.getpid(), signal.SIGHUP) raise Exception("bridge prefix duplicates with internet connection, autofix it and restart")
def _stopDnsmasq(self): self.lastScanRecord = None if self.leaseMonitor is not None: self.leaseMonitor.cancel() self.leaseMonitor = None if self.dnsmasqProc is not None: self.dnsmasqProc.terminate() self.dnsmasqProc.wait() self.dnsmasqProc = None WrtUtil.forceDelete(self.pidFile) WrtUtil.forceDelete(self.leasesFile) WrtUtil.forceDelete(self.hostsDir) WrtUtil.forceDelete(self.myhostnameFile)
def add_host(self, source_id, ip_data_dict): fn = os.path.join(self.hostsDir, source_id) itemDict = WrtUtil.dnsmasqHostFileToOrderedDict(fn) bChanged = False for ip, data in ip_data_dict.items(): if ip in itemDict: if "hostname" in data: if itemDict[ip] != data["hostname"]: itemDict[ip] = data["hostname"] bChanged = True else: del itemDict[ip] bChanged = True else: if "hostname" in data: itemDict[ip] = data["hostname"] bChanged = True if bChanged: WrtUtil.dictToDnsmasqHostFile(itemDict, fn) self.dnsmasqProc.send_signal(signal.SIGHUP)
def _stopDnsmasq(self): if self.dnsmasqProc is not None: self.dnsmasqProc.terminate() self.dnsmasqProc.wait() self.dnsmasqProc = None WrtUtil.forceDelete(self.hostsDir) WrtUtil.forceDelete(self.pidFile) WrtUtil.forceDelete(self.cfgFile)
def _loadManagerPlugins(self): # load manager plugin for name in self.param.pluginHub.getPluginList("manager"): assert name not in self.managerPluginDict self.managerPluginDict[name] = self.param.pluginHub.getPlugin("manager", name) # create manager data class _Stub: pass data = _Stub() data.etcDir = self.param.etcDir data.tmpDir = self.param.tmpDir data.varDir = self.param.varDir data.uuid = self.param.uuid data.plugin_hub = self.param.pluginHub data.prefix_pool = self.param.prefixPool data.manager_caller = self.param.managerCaller data.managers = { "traffic": self.param.trafficManager, "wan": self.param.wanManager, "lan": self.param.lanManager, } data.managers.update(self.managerPluginDict) # get init order tdict = dict() for name in self.managerPluginDict: tdict[name] = set(self.managerPluginDict[name].init_after) tlist = toposort.toposort_flatten(tdict) # init manager plugin for name in tlist: fn = os.path.join(self.param.etcDir, "manager-%s.json" % (name)) if os.path.exists(fn) and os.path.getsize(fn) > 0: cfgObj = WrtUtil.loadJsonEtcCfg(fn) else: cfgObj = dict() p = self.managerPluginDict[name] p.init2(cfgObj, self.param.tmpDir, self.param.varDir, data) logging.info("Manager plugin \"%s\" activated." % (p.full_name)) self.param.managerCaller.call("on_manager_init", p) self.param.managerCaller.add_manager(name, p)
def _getInstanceAndInfoFromEtcDir(self, pluginPrefix, cfgfilePrefix, name): # Returns (instanceName, cfgobj, tmpdir, vardir) ret = [] for fn in glob.glob( os.path.join(self.param.etcDir, "%s-%s*.json" % (cfgfilePrefix, name))): bn = os.path.basename(fn) instanceName = bn[len("%s-%s" % (cfgfilePrefix, name)):len(".json") * -1] if instanceName != "": instanceName = instanceName.lstrip("-") if instanceName != "": fullName = "%s-%s" % (name, instanceName) else: fullName = name if os.path.getsize(fn) > 0: cfgObj = WrtUtil.loadJsonEtcCfg(fn) else: cfgObj = dict() tmpdir = os.path.join(self.param.tmpDir, "%s-%s" % (pluginPrefix, fullName)) vardir = os.path.join(self.param.varDir, "%s-%s" % (pluginPrefix, fullName)) ret.append((instanceName, cfgObj, tmpdir, vardir)) if len(ret) == 0: instanceName = "" fullName = name cfgObj = dict() tmpdir = os.path.join(self.param.tmpDir, "%s-%s" % (pluginPrefix, fullName)) vardir = os.path.join(self.param.varDir, "%s-%s" % (pluginPrefix, fullName)) ret.append((instanceName, cfgObj, tmpdir, vardir)) return ret
def __init__(self, param): self.param = param self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__) self.defaultBridge = None self.lifPluginList = [] self.vpnsPluginList = [] self.propDict = dict() # dict<property-source,property-dict> self.clientDict = dict() # dict<ip,data> self.clientSourceDict = dict() # dict<ip,source_id> self.clientPropDict = dict( ) # dict<ip,dict<property-source,property-dict>> try: # create default bridge tmpdir = os.path.join(self.param.tmpDir, "bridge-default") os.mkdir(tmpdir) vardir = os.path.join(self.param.varDir, "bridge-default") WrtUtil.ensureDir(vardir) self.defaultBridge = _DefaultBridge(self, tmpdir, vardir) self.defaultBridge.init2( "wrtd-br", self.param.prefixPool.usePrefix(), self.param.trafficManager.get_l2_nameserver_port(), lambda source_id, ip_data_dict: self._clientAdd( source_id, ip_data_dict), lambda source_id, ip_data_dict: self._clientChange( source_id, ip_data_dict), lambda source_id, ip_list: self._clientRemove( source_id, ip_list)) self.logger.info("Default bridge started.") # start all lan interface plugins for name in self.param.pluginHub.getPluginList("lif"): for instanceName, cfgObj, tmpdir, vardir in self._getInstanceAndInfoFromEtcDir( "lif", "lan-interface", name): os.mkdir(tmpdir) WrtUtil.ensureDir(vardir) p = self.param.pluginHub.getPlugin("lif", name, instanceName) p.init2(instanceName, cfgObj, tmpdir, vardir) p.start() self.lifPluginList.append(p) self.logger.info("LAN interface plugin \"%s\" activated." % (p.full_name)) # start all vpn server plugins for name in self.param.pluginHub.getPluginList("vpns"): for instanceName, cfgObj, tmpdir, vardir in self._getInstanceAndInfoFromEtcDir( "vpns", "vpn-server", name): os.mkdir(tmpdir) WrtUtil.ensureDir(vardir) p = self.param.pluginHub.getPlugin("vpns", name, instanceName) p.init2( instanceName, cfgObj, tmpdir, vardir, self.param.prefixPool.usePrefix(), self.param.trafficManager.get_l2_nameserver_port(), lambda source_id, ip_data_dict: self._clientAdd( source_id, ip_data_dict), lambda source_id, ip_data_dict: self._clientChange( source_id, ip_data_dict), lambda source_id, ip_list: self._clientRemove( source_id, ip_list)) p.start() self.vpnsPluginList.append(p) self.logger.info("VPN server plugin \"%s\" activated." % (p.full_name)) if p.get_wan_service() is not None: self.param.trafficManager.add_wan_service( p.full_name, p.get_wan_service()) # send other-bridge-create event all_bridges = [self.defaultBridge ] + [x.get_bridge() for x in self.vpnsPluginList] for bridge in all_bridges: for other_bridge in all_bridges: if bridge == other_bridge: continue bridge.add_source(other_bridge.get_bridge_id()) except BaseException: self._dispose() raise
def run(self): WrtUtil.ensureDir(self.param.varDir) WrtUtil.mkDirAndClear(self.param.tmpDir) WrtUtil.mkDirAndClear(self.param.runDir) try: logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) logging.getLogger().setLevel(WrtUtil.getLoggingLevel(self.param.logLevel)) logging.info("Program begins.") # manipulate iptables if not self.param.abortOnError: WrtUtil.iptablesSetEmpty() else: if not WrtUtil.iptablesIsEmpty(): raise Exception("iptables is not empty, wrtd use iptables exclusively") # load configuration self._loadCfg() # load UUID if WrtCommon.loadUuid(self.param): logging.info("UUID generated: \"%s\"." % (self.param.uuid)) else: logging.info("UUID loaded: \"%s\"." % (self.param.uuid)) # write pid file with open(self.param.pidFile, "w") as f: f.write(str(os.getpid())) # load plugin hub self.param.pluginHub = PluginHub(self.param) logging.info("Plugin HUB loaded.") # load prefix pool self.param.prefixPool = PrefixPool(os.path.join(self.param.varDir, "prefix-pool.json")) logging.info("Prefix pool loaded.") # create our own resolv.conf with open(self.param.ownResolvConf, "w") as f: f.write("") # load manager caller self.param.managerCaller = ManagerCaller(self.param) logging.info("Manager caller initialized.") # create main loop DBusGMainLoop(set_as_default=True) self.param.mainloop = GLib.MainLoop() # business initialize self.param.trafficManager = WrtTrafficManager(self.param) self.param.wanManager = WrtWanManager(self.param) self.param.lanManager = WrtLanManager(self.param) self._loadManagerPlugins() self.interfaceTimer = GObject.timeout_add_seconds(0, self._interfaceTimerCallback) # enable ip forward if WrtUtil.readFile(self.param.procIpForwareFile) == "0": with open(self.param.procIpForwareFile, "w") as f: f.write("1") logging.info("IP forwarding enabled.") else: logging.warn("IP forwarding already enabled.") # start DBUS API server self.param.dbusMainObject = DbusMainObject(self.param) self.param.dbusIpForwardObject = DbusIpForwardObject(self.param) logging.info("DBUS-API server started.") # start main loop logging.info("Mainloop begins.") GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, self._sigHandlerINT, None) GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, self._sigHandlerTERM, None) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGHUP, self._sigHandlerHUP, None) self.param.mainloop.run() logging.info("Mainloop exits.") finally: if self.interfaceTimer is not None: GLib.source_remove(self.interfaceTimer) self.interfaceTimer = None if True: for p in self.managerPluginDict.values(): p.dispose() logging.info("Manager plugin \"%s\" deactivated." % (p.full_name)) self.managerPluginDict = dict() if self.param.lanManager is not None: self.param.lanManager.dispose() self.param.lanManager = None if self.param.wanManager is not None: self.param.wanManager.dispose() self.param.wanManager = None if self.param.trafficManager is not None: self.param.trafficManager.dispose() self.param.trafficManager = None logging.shutdown() shutil.rmtree(self.param.tmpDir) if self.bRestart: WrtUtil.restartProgram()
def prefixConvert(prefix): tl = prefix.split("/") return tl[0] + "/" + str(WrtUtil.ipMaskToLen(tl[1]))
def _dnsmasqLeaseChanged(self, monitor, file, other_file, event_type): if event_type != Gio.FileMonitorEvent.CHANGED: return try: newLeaseList = WrtUtil.readDnsmasqLeaseFile(self.leasesFile) addList = [] changeList = [] removeList = [] for item in newLeaseList: item2 = self.___dnsmasqLeaseChangedFind( item, self.lastScanRecord) if item2 is not None: if item[1] != item2[1] or item[3] != item2[ 3]: # mac or hostname change changeList.append(item) else: addList.append(item) for item in self.lastScanRecord: if self.___dnsmasqLeaseChangedFind(item, newLeaseList) is None: removeList.append(item) if len(addList) > 0: ipDataDict = dict() for expiryTime, mac, ip, hostname, clientId in addList: self.__dnsmasqLeaseChangedAddToIpDataDict( ipDataDict, ip, mac, hostname) if hostname != "": self.pObj.logger.info( "Client %s(IP:%s, MAC:%s) appeared." % (hostname, ip, mac)) else: self.pObj.logger.info("Client %s(%s) appeared." % (ip, mac)) for expiryTime, mac, ip, hostname, clientId in changeList: self.__dnsmasqLeaseChangedAddToIpDataDict( ipDataDict, ip, mac, hostname) # log is not needed for client change self.clientAddFunc(self.get_bridge_id(), ipDataDict) if len(changeList) > 0: ipDataDict = dict() for expiryTime, mac, ip, hostname, clientId in changeList: self.__dnsmasqLeaseChangedAddToIpDataDict( ipDataDict, ip, mac, hostname) # log is not needed for client change self.clientChangeFunc(self.get_bridge_id(), ipDataDict) if len(removeList) > 0: ipList = [x[2] for x in removeList] self.clientRemoveFunc(self.get_bridge_id(), ipList) for expiryTime, mac, ip, hostname, clientId in removeList: if hostname != "": self.pObj.logger.info( "Client %s(IP:%s, MAC:%s) disappeared." % (hostname, ip, mac)) else: self.pObj.logger.info("Client %s(%s) disappeared." % (ip, mac)) self.lastScanRecord = newLeaseList except Exception: self.pObj.logger.error("Lease scan failed", exc_info=True) # fixme
def checkTrafficFacilityGroup(tfac_group): i = 0 for tfac in tfac_group: i += 1 if "facility-name" not in tfac: raise TfacException( "Lacking \"facility-name\" for facility No.%d." % (i)) if "facility-type" not in tfac: raise TfacException( "Lacking \"facility-type\" for facility \"%s\"." % (tfac["facility-name"])) if tfac["facility-type"] == "nameserver": if "target" not in tfac: raise TfacException("Lacking \"target\" for facility \"%s\"." % (tfac["facility-name"])) if not isinstance(tfac["target"], list): raise TfacException( "Type of \"target\" is invalid for facility \"%s\"." % (tfac["facility-name"])) for item in tfac["target"]: msg = "Some element in \"target\" is invalid for facility \"%s\"." % ( tfac["facility-name"]) if ":" in item: tlist = item.split(":") if len(tlist) != 2: raise TfacException(msg) if not WrtUtil.is_int(tlist[1]): raise TfacException(msg) if "domain-list" not in tfac: raise TfacException( "Lacking \"domain-list\" for facility \"%s\"." % (tfac["facility-name"])) if not isinstance(tfac["domain-list"], list): raise TfacException( "Type of \"domain-list\" is invalid for facility \"%s\"." % (tfac["facility-name"])) for item in tfac["domain-list"]: if not isinstance(item, str): raise TfacException( "Some element in \"domain-list\" is invalid for facility \"%s\"." % (tfac["facility-name"])) continue if tfac["facility-type"] == "gateway": if "target" not in tfac: raise TfacException("Lacking \"target\" for facility \"%s\"." % (tfac["facility-name"])) msg = "Invalid \"target\" for facility \"%s\"." % ( tfac["facility-name"]) if not isinstance(tfac["target"], list): raise TfacException(msg) if len(tfac["target"]) != 2: raise TfacException(msg) if tfac["target"][0] is not None and not isinstance( tfac["target"][0], str): raise TfacException(msg) if tfac["target"][1] is not None and not isinstance( tfac["target"][1], str): raise TfacException(msg) if "network-list" not in tfac: raise TfacException( "Lacking \"network-list\" for facility \"%s\"." % (tfac["facility-name"])) if not isinstance(tfac["network-list"], list): raise TfacException( "Type of \"network-list\" is invalid for facility \"%s\"." % (tfac["facility-name"])) for item in tfac["network-list"]: msg = "Some element in \"domain-list\" is invalid for facility \"%s\"." % ( tfac["facility-name"]) if not isinstance(item, str): raise TfacException(msg) try: tnet = ipaddress.IPv4Network(item) if tnet.is_private: raise TfacException(msg) if re.match("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", item.split("/")[0]) is None: raise TfacException(msg) except ipaddress.AddressValueError: raise TfacException(msg) except ipaddress.NetmaskValueError: raise TfacException(msg) except ValueError: raise TfacException(msg) continue raise TfacException("Invalid \"facility-type\" for facility \"%s\"." % (tfac["facility-name"]))