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 extractDomainRecordValue(self, recordInfo): value = None try: value = recordInfo['Value'] except Exception,e: DDNSUtils.err("Failed to get domain record value from {0}".format(recordInfo)) DDNSUtils.err("Exception:\n{0}".format(e))
def extractDomainRecordId(self, recordInfo): id = None try: id = recordInfo['RecordId'] except Exception,e: DDNSUtils.err("Failed to get domain record id from {0}".format(recordInfo)) DDNSUtils.err("Exception:\n{0}".format(e))
def addDomainRecord(self, localRecord, currentPublicIP): if not localRecord.subDomain: DDNSUtils.err("You must specify subdomain name.") return False if not currentPublicIP: DDNSUtils.err("Current public ip is empty.") return False # TODO addResult = self.resolver.addDomainRecord(localRecord.domain, rr=localRecord.subDomain, value=currentPublicIP) if not addResult: DDNSUtils.err("Failed to add domain record") return False if not self.config.save(localRecord.alias, "id", addResult['RecordId']): DDNSUtils.err("Failed to save domain record id to config file") return False if not self.config.save(localRecord.alias, "value", currentPublicIP): DDNSUtils.err("Failed to save domain record value to config file") return False return True
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 fetch_remote_record(self, local_record): """ Fetch RemoteDomainReord from Aliyun server by using LocalDomainRecord info :param LocalDomainRecord :return: RemoteDomainRecord or None """ # Aliyun use fuzzy matching pattern for RR and type keyword fuzzy_matched_list = self.resolver.describe_domain_records(local_record.domainname, rr_keyword=local_record.rr, type_keyword=local_record.type) if not fuzzy_matched_list: DDNSUtils.err("Failed to fetch remote DomainRecords.") return None exact_matched_list = [] check_keys = ('DomainName', 'RR', 'Type') for rec in fuzzy_matched_list: if all(rec.get(key, None) == getattr(local_record, key.lower()) for key in check_keys): exact_matched_list.append(rec) if not exact_matched_list: return None if len(exact_matched_list) > 1: DDNSUtils.err("Duplicate DomainRecord in Aliyun: {rec.subdomain}.{rec.domainname}" .format(rec=local_record)) return None try: remote_record = RemoteDomainRecord(exact_matched_list[0]) except Exception as ex: raise ex return remote_record
def fetch_remote_record(self, local_record): """ Fetch RemoteDomainReord from Aliyun server by using LocalDomainRecord info :param LocalDomainRecord :return: RemoteDomainRecord or None """ # Aliyun use fuzzy matching pattern for RR and type keyword fuzzy_matched_list = self.resolver.describe_domain_records(local_record.domainname, rr_keyword=local_record.rr, type_keyword=local_record.type) if not fuzzy_matched_list: DDNSUtils.err("Failed to fetch remote DomainRecords.") return None exact_matched_list = [] check_keys = ('DomainName', 'RR', 'Type') for rec in fuzzy_matched_list: if all(rec.get(key, None) == getattr(local_record, key.lower()) for key in check_keys): exact_matched_list.append(rec) if not exact_matched_list: return None if len(exact_matched_list) > 1: DDNSUtils.err("Duplicate DomainRecord in Aliyun: {rec.subdomain}.{rec.domainname}" .format(rec=local_record)) return None try: remote_record = RemoteDomainRecord(exact_matched_list[0]) except: return None return remote_record
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 getRemoteDomainRecordList(self, domain): if not domain: DDNSUtils.err("getDomainReordId: You must specify domain name.") return None # try get domain record id domainRecordList = self.resolver.describeDomainRecords(domain) return domainRecordList
def save(self, section, option, value): self.configParser.set(section, option, value) try: with open(self.configFile, 'wb') as cf: self.configParser.write(cf) except: DDNSUtils.err("Failed to save the config value") return False 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 matchRemoteDomainRecord(self, domain, subDomain, type): if not domain: DDNSUtils.err("matchRemoteDomainRecord: You must specify domain.") return None if not subDomain: DDNSUtils.err( "matchRemoteDomainRecord: You must specify sub_domain.") return None if not type: DDNSUtils.err("matchRemoteDomainRecord: You must specify type.") return None remoteRecordList = self.resolver.describeDomainRecords( domain, rrKeyword=subDomain, typeKeyword=type) if not remoteRecordList: return None if len(remoteRecordList) > 1: DDNSUtils.err( "Duplicate domain records set in Aliyun: (sub_domain: %s, domain: %s)" % (subDomain, domain)) return None return remoteRecordList[0]
def sync(self, localRecord, currentPublicIP): if not localRecord.id: DDNSUtils.err("You must specify domain record id.") return False if not localRecord.subDomain: DDNSUtils.err("You must specify subdomain name.") return False if not currentPublicIP: DDNSUtils.err("Current public ip is empty.") return False result = self.resolver.updateDomainRecord(localRecord.id, rr=localRecord.subDomain, value=currentPublicIP) # if we update domain record successfully, save current domain record value to config file if result is True: if not self.config.save(localRecord.alias, "value", currentPublicIP): DDNSUtils.err( "Failed to save domain record value to config file") return False return result
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 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 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 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 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))
def sync(self, localRecord, currentPublicIP): if not localRecord.id: DDNSUtils.err("You must specify domain record id.") return False if not localRecord.subDomain: DDNSUtils.err("You must specify subdomain name.") return False if not currentPublicIP: DDNSUtils.err("Current public ip is empty.") return False result = self.resolver.updateDomainRecord(localRecord.id, rr=localRecord.subDomain, value=currentPublicIP) # if we update domain record successfully, save current domain record value to config file if result is True: if not self.config.save(localRecord.alias, "value", currentPublicIP): DDNSUtils.err("Failed to save domain record value to config file") return False return result
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 matchRemoteDomainRecord(self, domain, subDomain, type): if not domain: DDNSUtils.err("matchRemoteDomainRecord: You must specify domain.") return None if not subDomain: DDNSUtils.err("matchRemoteDomainRecord: You must specify sub_domain.") return None if not type: DDNSUtils.err("matchRemoteDomainRecord: You must specify type.") return None remoteRecordList = self.resolver.describeDomainRecords(domain, rrKeyword=subDomain, typeKeyword=type) if not remoteRecordList: return None return remoteRecordList[0]