def update_dns_record(self, dns_record, public_ip): """ Update a dns record at dns provider side with a given dns record """ DDNSUtils.info( 'Updating value [{rec.value}] for [{rec.rr}.{rec.domainname}]'. format(rec=dns_record)) acsClient = AcsClient(ak=self.access_key_id, secret=self.access_Key_secret, region_id='cn-hangzhou') request = UpdateDomainRecordRequest.UpdateDomainRecordRequest() request.set_RR(dns_record.rr) request.set_Type(dns_record.type) request.set_Value(dns_record.value) request.set_RecordId(dns_record.recordid) request.set_TTL(dns_record.ttl) request.set_accept_format('json') try: result = acsClient.do_action_with_exception(request) return result except Exception as exception: DDNSUtils.err( 'Failed to update value [{rec.value}] for [{rec.rr}.{rec.domainname}]' .format(rec=dns_record)) raise exception
def sync(self, localRecord, currentPublicIP): remoteRecord = self.matchRemoteDomainRecord(localRecord.domain, localRecord.subDomain, localRecord.type) if not remoteRecord: return self.addDomainRecord(localRecord, currentPublicIP) remoteRecordId = self.extractDomainRecordId(remoteRecord) if not remoteRecordId: DDNSUtils.err("Failed to extract domain id from remote domain record desc") return False # save domain record id for future reference if not self.config.save(localRecord.alias, "id", remoteRecordId): DDNSUtils.err("Failed to save domain record id to config file") return False remoteRecordValue = self.extractDomainRecordValue(remoteRecord) if not remoteRecordValue: DDNSUtils.err("Failed to extract domain value from remote domain record desc") return False # check whether to update the remote record or just update current profile; if currentPublicIP == remoteRecordValue and currentPublicIP != localRecord.value: # sync local record if not self.config.save(localRecord.alias, "value", currentPublicIP): DDNSUtils.err("Failed to save domain record value to config file") return False return True; # Now, check if domain record value is different with current public ip or not if currentPublicIP != remoteRecordValue: return self.updateDomainRecord(localRecord, currentPublicIP) if self.config.debug: DDNSUtils.info("No change with domain record value on remote server, skip it...") return True
def syncFirstTime(self, localRecord, currentPublicIP): remoteRecord = self.matchRemoteDomainRecord(localRecord.domain, localRecord.subDomain, localRecord.type) if not remoteRecord: DDNSUtils.err("Failed to match remote domain record for {0}.{1}" "".format(localRecord.subDomain, localRecord.domain)) return False remoteRecordId = self.extractDomainRecordId(remoteRecord) if not remoteRecordId: DDNSUtils.err("Failed to extract domain id from remote domain record desc") return False # save domain record id for future reference if not self.config.save(localRecord.alias, "id", remoteRecordId): DDNSUtils.err("Failed to save domain record id to config file") return False remoteRecordValue = self.extractDomainRecordValue(remoteRecord) if not remoteRecordValue: DDNSUtils.err("Failed to extract domain value from remote domain record desc") return False # Now, check if domain record value is different with current public ip or not if currentPublicIP != remoteRecordValue or currentPublicIP != localRecord.value: return self.sync(localRecord, currentPublicIP) if self.config.debug: DDNSUtils.info("No change with domain record value on remote server, skip it...") return True
def from_config_file(cls, configFilePath): """ Construct a Config object from a given config file Args: configFilePath: path to the given config file Returns: Instance of Config """ parser = ConfigParser() if not parser.read(configFilePath): DDNSUtils.err_and_exit("Failed to read config file.") try: access_key_id = parser.get("ApiProvider", "access_key_id") access_key_secret = parser.get("ApiProvider", "access_key_secret") if not access_key_id or not access_key_secret: DDNSUtils.err_and_exit("Invalid access_id or access_key in config file.") apiProviderInfo = ApiProviderInfo(access_key_id, access_key_secret) DDNSUtils.info("Read Access Key Id: [{0}]".format(access_key_id)) DDNSUtils.info("Read Access Key Secret: [{0}]".format(access_key_secret)) recordsSections = [s for s in parser.sections() if s.startswith("DomainNameToUpdate") ] records = [] for record in recordsSections: domain = parser.get(record,'domain') type = parser.get(record,'type') subDomains = parser.get(record,'sub_domain').split(',') if not domain or not type or not subDomains: DDNSUtils.err_and_exit("Invalid domian record.") DDNSUtils.info("Read Domain: [{0}]".format(domain)) DDNSUtils.info("Read Sub Domains: [{0}]".format(subDomains)) DDNSUtils.info("Read Type: [{0}]".format(type)) for subDomain in subDomains: record = DomainSection(domain, subDomain.strip(), type) records.append(record) config = cls(apiProviderInfo, records) return config except ValueError as ex: DDNSUtils.err_and_exit("Invalid debug in config: {0}".format(ex)) except NoSectionError as ex: DDNSUtils.err_and_exit("Invalid config: {0}".format(ex)) except NoOptionError as ex: DDNSUtils.err_and_exit("Invalid config: {0}".format(ex))
def main(): """ Main routine """ config = DDNSConfig() record_manager = DDNSDomainRecordManager(config) for local_record in record_manager.local_record_list: family = AF_INET if local_record.type == 'A' else AF_INET6 interface = local_record.interface if interface is None: current_public_ip = { AF_INET: DDNSUtils.get_current_public_ip(), AF_INET6: DDNSUtils.get_current_public_ipv6() } else: current_public_ip = { AF_INET: DDNSUtils.get_interface_address(interface), AF_INET6: DDNSUtils.get_interface_address(interface, AF_INET6) } if not current_public_ip: DDNSUtils.info( "Unable to get current IP for [{rec.subdomain}.{rec.domainname}.{rec.type}]" .format(rec=local_record)) continue dns_resolved_ip = DDNSUtils.get_dns_resolved_ip( local_record.subdomain, local_record.domainname, family) if current_public_ip[family] == dns_resolved_ip: DDNSUtils.info("Skipped as no changes for DomainRecord" \ "[{rec.subdomain}.{rec.domainname}.{rec.type}]".format(rec=local_record)) continue # If current public IP doesn't equal to current DNS resolved ip, only in three cases: # 1. The new synced IP for remote record in Aliyun doesn't take effect yet # 2. remote record's IP in Aliyun server has changed # 3. current public IP is changed remote_record = record_manager.fetch_remote_record(local_record) if not remote_record: DDNSUtils.err("Failed finding remote DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue if current_public_ip[family] == remote_record.value: DDNSUtils.info("Skipped as we already updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # if we can fetch remote record and record's value doesn't equal to public IP record_type = 'A' if family == AF_INET else 'AAAA' sync_result = record_manager.update(remote_record, current_public_ip[family], record_type) if not sync_result: DDNSUtils.err("Failed updating DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) else: DDNSUtils.info("Successfully updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record))
def get_dns_resolved_ip(self): if self.subDomainName == "@": hostname = self.domainName else: hostname = "{0}.{1}".format(self.subDomainName, self.domainName) try: ip_addr = socket.gethostbyname(hostname) DDNSUtils.info("RR value read: [{0}] for [{1}]".format( ip_addr, hostname)) return ip_addr except Exception as exception: DDNSUtils.err( "Failed to read ip address for [{0}]".format(hostname)) raise exception
def get_dns_record(self, dns_section): """ Get the dns record from dns provider by a given dns section """ DDNSUtils.info( "Reading dns records for [{section.subDomainName}.{section.domainName}], type=[{section.type}]" .format(section=dns_section)) acsClient = AcsClient(ak=self.access_key_id, secret=self.access_Key_secret, region_id='cn-hangzhou') request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest() request.set_DomainName(dns_section.domainName) request.set_accept_format('json') result = acsClient.do_action_with_exception(request) result = json.loads(result.decode('utf8')) dns_record_list = result['DomainRecords']['Record'] if not dns_record_list: DDNSUtils.err( "Failed to fetch dns resolution records for [{rec.domainName}] by rr={rec.subDomainName} and type={rec.type}" .format(rec=dns_section)) return None matched_records = [] for record in dns_record_list: if record['DomainName'] == dns_section.domainName and record[ 'RR'] == dns_section.subDomainName and record[ 'Type'] == dns_section.type: matched_records.append(record) if not matched_records: return None if len(matched_records) > 1: DDNSUtils.err( 'Duplicate dns resolution records: [{rec.subDomainName}.{rec.domaiNname}]' .format(rec=dns_section)) try: dns_record = DnsRecord(matched_records[0]) except Exception as exception: raise exception return dns_record
def main(): """ Main routine """ config = DDNSConfig() record_manager = DDNSDomainRecordManager(config) # get current public ip for this server if config.pifn_enable: current_public_ip = DDNSUtils.get_interface_address( config.pifn_interface) else: current_public_ip = DDNSUtils.get_current_public_ip() if not current_public_ip: DDNSUtils.err_and_exit("Failed to get current public IP") for local_record in record_manager.local_record_list: if local_record.subdomain == '*': dns_resolved_ip = DDNSUtils.get_dns_resolved_ip( 'xxx', local_record.domainname) else: dns_resolved_ip = DDNSUtils.get_dns_resolved_ip( local_record.subdomain, local_record.domainname) if local_record.type == "AAAA": current_ip = DDNSUtils.get_interface_ipv6_address( local_record.interface) else: current_ip = current_public_ip if current_ip == dns_resolved_ip: DDNSUtils.info("Skipped as no changes for DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # If current public IP doesn't equal to current DNS resolved ip, only in three cases: # 1. The new synced IP for remote record in Aliyun doesn't take effect yet # 2. remote record's IP in Aliyun server has changed # 3. current public IP is changed remote_record = record_manager.fetch_remote_record(local_record) if not remote_record: DDNSUtils.err("Failed finding remote DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue if current_ip == remote_record.value: DDNSUtils.info("Skipped as we already updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # if we can fetch remote record and record's value doesn't equal to public IP sync_result = record_manager.update(remote_record, current_ip, local_record.type) if not sync_result: DDNSUtils.err("Failed updating DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) else: DDNSUtils.info("Successfully updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record))
def perform_ddns(self): """ Perform the ddns process when everything is ready """ current_public_ip = DDNSUtils.get_current_public_ip() if not current_public_ip: DDNSUtils.err_and_exit("Failed to get local public IP") DDNSUtils.info( "Local public ip address read: [{0}]".format(current_public_ip)) for record_to_update in self.configuration.recordsToUpdate: dns_resolved_ip = record_to_update.get_dns_resolved_ip() if current_public_ip == dns_resolved_ip: DDNSUtils.info( "Skipped as no changes for DomainRecord: [{rec.subDomainName}.{rec.domainName}]" .format(rec=record_to_update)) continue # If current public IP doesn't equal to current DNS resolved ip, only in three cases: # 1. The new synchronized IP for remote record in api provider doesn't take effect yet # 2. remote record's IP in Aliyun server has changed # 3. current public IP is changed dns_record = self.get_dns_record(record_to_update) if not dns_record: DDNSUtils.err( "Failed to get dns resolution record for [{rec.subDomainName}.{rec.domainName}]" .format(rec=record_to_update)) continue if current_public_ip == dns_record.value: DDNSUtils.info( "Skipped: dns record already updated: [{rec.subDomainName}.{rec.domainName}]" .format(rec=record_to_update)) continue dns_record.value = current_public_ip result = self.update_dns_record(dns_record, current_public_ip) if not result: DDNSUtils.err( "Failed to update dns record: [{rec.subDomainName}.{rec.domainName}]" .format(rec=record_to_update)) else: DDNSUtils.info( "Successfully update dns record: [{rec.subDomainName}.{rec.domainName}]" .format(rec=record_to_update))
def syncFirstTime(self, localRecord, currentPublicIP): remoteRecord = self.matchRemoteDomainRecord(localRecord.domain, localRecord.subDomain, localRecord.type) if not remoteRecord: DDNSUtils.err("Failed to match remote domain record for {0}.{1}" "".format(localRecord.subDomain, localRecord.domain)) return False remoteRecordId = self.extractDomainRecordId(remoteRecord) if not remoteRecordId: DDNSUtils.err( "Failed to extract domain id from remote domain record desc") return False # save domain record id for future reference localRecord.id = remoteRecordId if not self.config.save(localRecord.alias, "id", remoteRecordId): DDNSUtils.err("Failed to save domain record id to config file") return False remoteRecordValue = self.extractDomainRecordValue(remoteRecord) if not remoteRecordValue: DDNSUtils.err( "Failed to extract domain value from remote domain record desc" ) return False # Now, check if domain record value is different with current public ip or not if currentPublicIP != remoteRecordValue or currentPublicIP != localRecord.value: return self.sync(localRecord, currentPublicIP) if self.config.debug: DDNSUtils.info( "No change with domain record value on remote server, skip it..." ) return True
def main(method, *args): """ Main routine """ config = DDNSConfig() record_manager = DDNSDomainRecordManager(config) # get current public ip for this server def switch(m): switcher = { "net": lambda: DDNSUtils.get_current_public_ip(), "static": lambda: args[0], } return switcher.get(m, lambda: "net|static 10.0.0.1") func = switch(method) current_public_ip = func() if not current_public_ip: DDNSUtils.err_and_exit("Failed to get current public IP") for local_record in record_manager.local_record_list: dns_resolved_ip = DDNSUtils.get_dns_resolved_ip(local_record.subdomain, local_record.domainname) if current_public_ip == dns_resolved_ip: DDNSUtils.info("Skipped as no changes for DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # If current public IP doesn't equal to current DNS resolved ip, only in three cases: # 1. The new synced IP for remote record in Aliyun doesn't take effect yet # 2. remote record's IP in Aliyun server has changed # 3. current public IP is changed remote_record = record_manager.fetch_remote_record(local_record) if not remote_record: DDNSUtils.err("Failed finding remote DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue if current_public_ip == remote_record.value: DDNSUtils.info("Skipped as we already updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # if we can fetch remote record and record's value doesn't equal to public IP sync_result = record_manager.update(remote_record, current_public_ip) if not sync_result: DDNSUtils.err("Failed updating DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) else: DDNSUtils.info("Successfully updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record))
def main(): """ Main routine """ config = DDNSConfig() record_manager = DDNSDomainRecordManager(config) # get current public ip for this server current_public_ip = DDNSUtils.get_current_public_ip() if not current_public_ip: DDNSUtils.err_and_exit("Failed to get current public IP") for local_record in record_manager.local_record_list: dns_resolved_ip = DDNSUtils.get_dns_resolved_ip(local_record.subdomain, local_record.domainname) if current_public_ip == dns_resolved_ip: DDNSUtils.info("Skipped as no changes for DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # If current public IP doesn't equal to current DNS resolved ip, only in three cases: # 1. The new synced IP for remote record in Aliyun doesn't take effect yet # 2. remote record's IP in Aliyun server has changed # 3. current public IP is changed remote_record = record_manager.fetch_remote_record(local_record) if not remote_record: DDNSUtils.err("Failed finding remote DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue if current_public_ip == remote_record.value: DDNSUtils.info("Skipped as we already updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) continue # if we can fetch remote record and record's value doesn't equal to public IP sync_result = record_manager.update(remote_record, current_public_ip) if not sync_result: DDNSUtils.err("Failed updating DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record)) else: DDNSUtils.info("Successfully updated DomainRecord" \ "[{rec.subdomain}.{rec.domainname}]".format(rec=local_record))
# get current public ip currentPublicIP = utils.getCurrentPublicIP() if not currentPublicIP: DDNSUtils.err_and_exit("Failed to get current public ip") for localRecord in config.localDomainRecordList: # if we don't have domain record's id in config file, then we never sync to server before if not localRecord.id or not localRecord.value: result = helper.syncFirstTime(localRecord, currentPublicIP) if result is False: DDNSUtils.err_and_exit( "Failed doing the first time sync for record:{0}".format( localRecord.alias)) continue DDNSUtils.info("Successfully sync done for record:{0}".format( localRecord.alias)) continue # if current public ip is not the same as the one in server if currentPublicIP != localRecord.value: if config.debug: DDNSUtils.info( "current public ip is:{0}, server ip is:{1}".format( currentPublicIP, localRecord.value)) result = helper.sync(localRecord, currentPublicIP) if result is False: DDNSUtils.err_and_exit( "Failed to sync the current public IP for record:{0}". format(localRecord.alias))
helper = DDNSHelper(config) # get current public ip currentPublicIP = utils.getCurrentPublicIP() if not currentPublicIP: DDNSUtils.err_and_exit("Failed to get current public ip") for localRecord in config.localDomainRecordList: # if we don't have domain record's id in config file, then we never sync to server before if not localRecord.id or not localRecord.value: result = helper.syncFirstTime(localRecord,currentPublicIP) if result is False: DDNSUtils.err_and_exit("Failed doing the first time sync for record:{0}".format(localRecord.alias)) continue DDNSUtils.info("Successfully sync done for record:{0}".format(localRecord.alias)) continue # if current public ip is not the same as the one in server if currentPublicIP != localRecord.value: if config.debug: DDNSUtils.info("current public ip is:{0}, server ip is:%s".format(currentPublicIP, localRecord.value)) result = helper.sync(localRecord, currentPublicIP) if result is False: DDNSUtils.err_and_exit("Failed to sync the current public IP for record:{0}".format(localRecord.alias)) DDNSUtils.info("Successfully sync done on:{0}".format(utils.getCurrentTime())) sys.exit(0) # all done for one record
help='The access key secret assigned by ddns api provider.') argParser.add_argument( "--config", '-c', help= 'The configuration file to refer to for ddns process. If this option is set, all other options will be ignored.' ) args = argParser.parse_args() if args.config: CONFIG_FILE_PATH = args.config if not os.path.exists(CONFIG_FILE_PATH): DDNSUtils.err_and_exit('File not found: {0}'.format(CONFIG_FILE_PATH)) DDNSUtils.info( "Loading configuration from file: {0}".format(CONFIG_FILE_PATH)) config = Config.from_config_file(CONFIG_FILE_PATH) coordinator = DDNSCoordinator(config) coordinator.perform_ddns() pass elif not args.domains or not args.access_key_id or not args.access_key_secret: argParser.print_help() else: DDNSUtils.info("Loading configuration from arguments.") config = Config.from_cli_options(args.domains, args.access_key_id, args.access_key_secret, args.type) coordinator = DDNSCoordinator(config) coordinator.perform_ddns() pass
if __name__ == "__main__": config = DDNSConfig(CONF_FILE) config.validate() utils = DDNSUtils(config.debug) helper = DDNSHelper(config) # get current public ip currentPublicIP = utils.getCurrentPublicIP() if not currentPublicIP: DDNSUtils.err_and_exit("Failed to get current public ip") for localRecord in config.localDomainRecordList: # try to sync all record, if config.debug: DDNSUtils.info("current public ip is:{0}, cached ip is:{1}".format(currentPublicIP, localRecord.value)) result = helper.sync(localRecord, currentPublicIP); if result is False: DDNSUtils.err_and_exit("Failed doing the first time sync for record:{0}".format(localRecord.alias)) continue DDNSUtils.info("Successfully sync done for record:{0}".format(localRecord.alias)) continue # all done for one record if config.debug: DDNSUtils.info("No changes,skipped...") continue