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 _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 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()
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 _upCallback(self): serverName, dummy, dummy = self.curProviderList[0].get_server() logging.info("CGFW VPN %s connected." % (serverName)) CgfwCommon.ntfacSendEntityNameserverNew( self.param.nameServerList, CgfwCommon.getDomainList(self.param.gfwDomainDir)) CgfwCommon.ntfacSendEntityGatewayNew((None, "cgfw"), [ x.with_netmask for x in CgfwCommon.getPrefixList(self.param.gfwDir) ]) self.upCalled = True
def _runDnsmasq(self): # trick: dnsmasq debug is seldomly needed trickDebug = False # generate dnsmasq config file. so that GFWed domain is resolved # through our nameservers, non-GFWed domain is resolved through the # original nameservers. dnsmasq listens on 127.0.0.1:80 buf = "" if trickDebug: buf += "log-queries=extra\n" buf += "log-facility=%s\n" % (os.path.join(self.param.tmpDir, "dnsmasq.debug")) buf += "\n" buf += "strict-order\n" buf += "bind-interfaces\n" # don't listen on 0.0.0.0 buf += "listen-address=127.0.0.1\n" buf += "user=root\n" buf += "group=root\n" buf += "domain-needed\n" buf += "bogus-priv\n" buf += "no-hosts\n" buf += "\n" buf += "resolv-file=%s\n" % (os.path.join(self.param.tmpDir, "orig-resolve.conf")) buf += "no-poll\n" buf += "\n" for domain in CgfwCommon.getDomainList(self.param.gfwDomainDir): for ns in self.param.nameServerList: buf += "server=/%s/%s\n" % (domain, ns) cfgf = os.path.join(self.param.tmpDir, "dnsmasq.conf") with open(cfgf, "w") as f: f.write(buf) # generate alternative resolve.conf file with open(os.path.join(self.param.tmpDir, "orig-resolve.conf"), "w") as f: f.write(self.resloveFileContent) # run dnsmasq process cmd = "/usr/sbin/dnsmasq" cmd += " --keep-in-foreground" cmd += " --conf-file=\"%s\"" % (cfgf) cmd += " --pid-file=%s" % (os.path.join(self.param.tmpDir, "dnsmasq.pid")) proc = subprocess.Popen(cmd, shell=True, universal_newlines=True) return proc
def GetPrefixList(self): nets = CgfwCommon.getPrefixList(self.param.gfwDir) nets = [x.with_netmask.split("/") for x in nets] return nets
def cmdCheck(self): for cgfwCfg in CgfwCommon.getCgfwCfgList(self.param.etcDir): if cgfwCfg.vtype == "pptp": self._checkPptpVpn(cgfwCfg) else: assert False