def getCgfwCfg(): ret = dict() for cgfwFile in sorted(glob.glob("/etc/cgfw/*.cgfw")): cfg = configparser.SafeConfigParser() cfg.read(cgfwFile) if cfg.has_option("cgfw-entry", "provider"): provider = CgfwUtil.stripComment(cfg.get("cgfw-entry", "provider")) if provider not in ["myvpnpp"]: raise Exception("invalid provider %s in cgfw-entry" % (provider)) ret[provider] = dict() else: raise Exception("no type in cgfw-entry") if cfg.has_option("cgfw-entry", "username"): ret[provider]["username"] = CgfwUtil.stripComment(cfg.get("cgfw-entry", "username")) else: raise Exception("no username in cgfw-entry") if cfg.has_option("cgfw-entry", "password"): ret[provider]["password"] = CgfwUtil.stripComment(cfg.get("cgfw-entry", "password")) else: raise Exception("no password in cgfw-entry") return ret
def run(self): CgfwUtil.mkDirAndClear(self.param.tmpDir) try: sys.stdout = StdoutRedirector(os.path.join(self.param.tmpDir, "fpemud-cgfw.out")) sys.stderr = sys.stdout logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) logging.getLogger().setLevel(logging.INFO) # check configuration if len(CgfwCommon.getCgfwCfgList(self.param.etcDir)) == 0: raise Exception("no cgfw config file") # create main loop DBusGMainLoop(set_as_default=True) self.param.mainloop = GLib.MainLoop() self.param.dbusMainObject = DbusMainObject(self.param, self) # write pid file with open(os.path.join(self.param.tmpDir, "fpemud-cgfw.pid"), "w") as f: f.write(str(os.getpid())) # modify dns server configuration with open("/etc/resolv.conf", "r") as f: self.resloveFileContent = f.read() self.dnsmasqProc = self._runDnsmasq() with open("/etc/resolv.conf", "w") as f: f.write("# Generated by fpemud-cgfw\n") f.write("nameserver 127.0.0.1\n") logging.info("DNS resolution path modified.") # run vpn client GObject.timeout_add_seconds(0, self._timeoutCallback) # 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) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGUSR1, self._sigHandlerUSR1, None) self.param.mainloop.run() logging.info("Mainloop exits.") finally: if self.vpnClientPidWatch is not None: GLib.source_remove(self.vpnClientPidWatch) if self.vpnClientProc is not None: self.vpnClientProc.terminate() self.vpnClientProc.wait() # revert dns resolution path if self.resloveFileContent is not None: with open("/etc/resolv.conf", "w") as f: f.write(self.resloveFileContent) if self.dnsmasqProc is not None: self.dnsmasqProc.terminate() self.dnsmasqProc.wait() logging.shutdown() shutil.rmtree(self.param.tmpDir)
def _syncPptpVpnScript(self, cgfwCfg): # 1. add the same routes again when ppp interface changes from spoofing-up to real-up # it is because pppd deletes the original ppp interface and add a new ppp interface with the same name, this implementation sucks # 2. add nat rules fn = "/etc/ppp/ip-up.d/99-pptp-%s" % (cgfwCfg.name) + ".sh" CgfwUtil.printInfoNoNewLine(" Modifying %s..." % (fn)) buf = "" buf += "#!/bin/bash\n" buf += "\n" buf += "if [ \"$6\" == \"%s\" ] ; then\n" % (cgfwCfg.name) if True: for ip in self.param.nameServerList: buf += " /bin/route add -host %s dev %s\n" % (ip, cgfwCfg.interface) buf += "\n" if True: for net in CgfwCommon.getPrefixList(self.param.gfwDir): r = net.with_netmask.split("/") ip = r[0] mask = r[1] buf += " /bin/route add -net %s netmask %s dev %s\n" % (ip, mask, cgfwCfg.interface) buf += "\n" if True: pidf = os.path.join(self.param.tmpDir, "fpemud-cgfw.pid") buf += " if [ -f \"%s\" ] ; then\n" % (pidf) buf += " /bin/kill -10 $(/bin/cat \"%s\")\n" % (pidf) # send SIGUSR1 buf += " fi\n" buf += "fi\n" with open(fn, "w") as f: f.write(buf) print("Done.") # 1. routes are auto removed when ppp interface is removed # 2. remove nat rules fn = "/etc/ppp/ip-down.d/99-pptp-%s" % (cgfwCfg.name) + ".sh" CgfwUtil.printInfoNoNewLine(" Modifying %s..." % (fn)) buf = "" buf += "#!/bin/bash\n" buf += "\n" buf += "if [ \"$6\" == \"%s\" ] ; then\n" % (cgfwCfg.name) buf += " ;\n" buf += "fi\n" with open(fn, "w") as f: f.write(buf) print("Done.")
def getPrefixList(gfwDir): """Returns list of ipaddress.IPv4Network""" # get GFWed network list nets = [] for fn in os.listdir(gfwDir): fullfn = os.path.join(gfwDir, fn) with open(fullfn) as f: lineList = f.read().split("\n") for i in range(0, len(lineList)): line = CgfwUtil.getLineWithoutBlankAndComment(lineList[i]) if line is None: continue nets.append(ipaddress.IPv4Network(line)) # optimize network list nets.sort() i = 0 while i < len(nets): j = i + 1 while j < len(nets): if nets[i].overlaps(nets[j]): # big network is in front of small network, so we remove small network. # I think network can only "wholly contain" each other, does "partly contain" really exist? del nets[j] continue j += 1 i += 1 return nets
def _runVpnClient(self): assert self.vpnClientProc is None and self.vpnClientPidWatch is None # randomly select a config cgfwCfg = random.choice(CgfwCommon.getCgfwCfgList(self.param.etcDir)) # start vpn client process logging.info("CGFW VPN %s establishing." % (cgfwCfg.name)) if cgfwCfg.vtype == "pptp": cmd = "/usr/sbin/pppd call %s nodetach" % (cgfwCfg.name) self.vpnClientProc = subprocess.Popen(cmd, shell=True, universal_newlines=True) else: assert False # wait for the vpn interface time.sleep(0.1) while not CgfwUtil.interfaceExists(cgfwCfg.interface): if self.vpnClientProc.poll() is not None: self.vpnClientProc = None logging.info("CGFW VPN %s failed to establish, retry in %d seconds." % (cgfwCfg.name, self.param.retryTimeout)) GObject.timeout_add_seconds(self.param.retryTimeout, self._timeoutCallback) return time.sleep(1.0) # add routes for ip in self.param.nameServerList: CgfwUtil.shell("/bin/route add -host %s dev %s" % (ip, cgfwCfg.interface), "stdout") for net in CgfwCommon.getPrefixList(self.param.gfwDir): r = net.with_netmask.split("/") ip = r[0] mask = r[1] CgfwUtil.shell("/bin/route add -net %s netmask %s dev %s" % (ip, mask, cgfwCfg.interface), "stdout") # add dns server # dbusObj = dbus.SystemBus().get_object('org.freedesktop.resolve1', '/org/freedesktop/resolve1') # ifid = CgfwUtil.getInterfaceIfIndex(cgfwCfg.interface) # dbusObj.SetLinkDNS(ifid, [CgfwUtil.ip2ipar("8.8.8.8"), CgfwUtil.ip2ipar("8.8.4.4")], dbus_interface="org.freedesktop.resolve1.Manager") # logging.info("CGFW DNS server installed.") # watch vpn client process # bad things happen if the vpn process terminates before this operation self.vpnClientPidWatch = GLib.child_watch_add(self.vpnClientProc.pid, self._childWatchCallback) self.curCgfwCfg = cgfwCfg logging.info("CGFW VPN %s established." % (cgfwCfg.name)) self.param.dbusMainObject.VpnConnected(self.bDataChanged)
def getDomainList(gfwDomainDir): # get GFWed domain list ret = [] for fn in os.listdir(gfwDomainDir): fullfn = os.path.join(gfwDomainDir, fn) with open(fullfn) as f: lineList = f.read().split("\n") for i in range(0, len(lineList)): line = CgfwUtil.getLineWithoutBlankAndComment(lineList[i]) if line is None: continue ret.append(line) return ret
def cmdUpdate(self): CgfwUtil.printInfo("Checking IP ranges:") if True: prefixList = CgfwCommon.getPrefixList(self.param.gfwDir) CgfwUtil.printInfoNoNewLine(" Checking private network...") priList = CgfwUtil.getReservedIpv4NetworkList() for net in prefixList: for net2 in priList: if net.overlaps(net2): raise CgfwCmdException("GFWed prefix %s overlaps private network %s" % (net.with_prefixlen, net2.with_prefixlen)) print("Done.") CgfwUtil.printInfoNoNewLine(" Checking non-GFWed network...") try: lcmList = CgfwCommon.getLatestChinaMainLandIpv4NetworkList() for net in prefixList: for net2 in lcmList: if net.overlaps(net2): raise CgfwCmdException("GFWed prefix %s overlaps non-GFWed network %s" % (net.with_prefixlen, net2.with_prefixlen)) print("Done.") except Exception as e: if isinstance(e, CgfwCmdException): raise else: print("Failed, but however it's better to continue.") CgfwUtil.printInfo("Modifying configuration files:") for cgfwCfg in CgfwCommon.getCgfwCfgList(self.param.etcDir): if cgfwCfg.vtype == "pptp": self._syncPptpVpnScript(cgfwCfg) else: assert False # send signal to daemon process if exists try: with open(os.path.join(self.param.tmpDir, "fpemud-cgfw.pid")) as f: os.kill(int(f.read()), signal.SIGHUP) except: pass
def run(self): try: logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) logging.getLogger().setLevel( CgfwUtil.getLoggingLevel(self.param.logLevel)) logging.info("Program begins.") # read configuration for section, data in CgfwCommon.getCgfwCfg().items(): tmpDir2 = os.path.join(self.param.tmpDir, section) if self.varDir is None: varDir2 = None else: varDir2 = os.path.join(self.param.varDir, section) self.curProviderList.append( CgfwCommon.getProvider(section, data, tmpDir2, varDir2)) # create main loop mainloop = GLib.MainLoop() # start business if not os.path.exists(self.param.tmpDir): os.makedirs(self.param.tmpDir) self.vpnRestartTimer = GObject.timeout_add_seconds( 0, self._vpnRestartTimerCallback) # start main loop logging.info("Mainloop begins.") GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, on_sig_int, None) GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, on_sig_term, None) mainloop.run() logging.info("Mainloop exits.") finally: logging.shutdown()