February 23, 2012

自动更新DNSPod记录

今天将社区和个人的转移到了dnspod,原因是dnspod 提供免费的智能解析(电信、教育网什么的可以制定不同的IP地址),并且有丰富的API,可以用脚本更新记录,于是我就fork了dnspod官方的python脚本,写了个动态更新DNS记录的脚本。

比花生壳强大多啦!!!!

#!/usr/bin/env python2
# -*- coding:utf8 -*-

import httplib, urllib
import socket
import argparse
import time
import fcntl
import struct
from xml.etree import ElementTree
from getpass import getpass

DEBUG = False
params = dict(
    login_email="", # replace with your email
    login_password="", # replace with your password
    format="json",
    domain_id=None, # replace with your domain_od, can get it by API Domain.List
    record_id=None, # replace with your record_id, can get it by API Record.List
    record_type='A', # replace with your record_id, can get it by API Record.List
    sub_domain="", # replace with your sub_domain
    record_line="默认",
)

def ddns(ip):
    params.update(dict(value=ip))
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/json"}
    conn = httplib.HTTPSConnection("dnsapi.cn")
    conn.request("POST", "/Record.Modify", urllib.urlencode(params), headers)
    
    response = conn.getresponse()
    if DEBUG: print response.status, response.reason
    data = response.read()
    if DEBUG: print data
    conn.close()
    return response.status == 200

def get_domain_id(domain_name):
    keys = ["login_email", "login_password"]
    _param = { k:v for k,v in params.iteritems() if k in keys }

    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/json"}
    conn = httplib.HTTPSConnection("dnsapi.cn")
    conn.request("POST", "/Domain.list", urllib.urlencode(_param), headers)
    response = conn.getresponse()
    id = None
    etree = ElementTree.parse(response)
    for domain in  etree.findall("domains/item"):
        if domain.find("name").text == domain_name:
            id = domain.find("id").text          
    conn.close()
    if id:
        return id
    else:
        raise Exception("Record '"+domain_name+"' not found!")

def get_record_id():
    keys = ["login_email", "login_password", "domain_id"]
    _param = { k:v for k,v in params.iteritems() if k in keys }
    subdomain = params["sub_domain"]
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/json"}
    conn = httplib.HTTPSConnection("dnsapi.cn")
    conn.request("POST", "/Record.List", urllib.urlencode(_param), headers)
    response = conn.getresponse()
    etree = ElementTree.parse(response)
    id = None
    for record in  etree.findall("records/item"):
        if record.find("name").text == subdomain:
            if DEBUG: print "Found", record.find("name").text
            if id:
                raise Exception("Multipule records of '"+subdomain+"' found. Please specify record id! ")
            id = record.find("id").text
    conn.close()
    if id:
        return id
    else:
        raise Exception("Record '"+subdomain+"' not found!")

def get_current_ip():
    """get current ip of """
    keys = ["login_email", "login_password", "domain_id"]
    record_id = params['record_id']
    _param = { k:v for k,v in params.iteritems() if k in keys }
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/json"}
    conn = httplib.HTTPSConnection("dnsapi.cn")
    conn.request("POST", "/Record.List", urllib.urlencode(_param), headers)
    response = conn.getresponse()
    etree = ElementTree.parse(response)
    cur_ip = None
    for record in  etree.findall("records/item"):
        if record.find("id").text == record_id:
            cur_ip = record.find("value").text
    conn.close()
    return cur_ip

def get_public_ip():
    """ get ip address from dnspod """
    if DEBUG: print "getting ip address from dnspod..."
    sock = socket.create_connection(('ns1.dnspod.net', 6666))
    ip = sock.recv(16)
    sock.close()
    return ip

def get_if_ip(ifname):
    """Get ip address by interface, Linux ONLY"""
    if DEBUG: print "getting ip address of", ifname
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
            s.fileno(),
            0x8915,  # SIOCGIFADDR
            struct.pack('256s', ifname[:15])
            )[20:24])

def parse_arg():
    parser = argparse.ArgumentParser(description="Update dns record on dnspod dynamically.")
    parser.add_argument( '-I', '--interval', help="Set test interval, default is 300 seconds." )
    parser.add_argument( '-i', '--interface', help="Set interface." )
    parser.add_argument( '-u','--username', help="Set login email." )
    parser.add_argument( '-p','--password', help="Set login password." )
    parser.add_argument( '-d','--domain', help="Set domain name." )
    parser.add_argument( '-s','-r','--subdomain', help="Set subdomain/record name." )
    parser.add_argument( '--domain-id', help="Set domain id." )
    parser.add_argument( '--record-id', help="Set record id." )
    parser.add_argument( '--debug',action="store_true", help="Show debug outputs." )
    return parser.parse_args()

if __name__ == '__main__':
    
    args = parse_arg()
    
    # confiure
    DEBUG = args.debug or False
    interval = args.interval or 300

    #Set domain name
    domain = args.domain or raw_input("Domain name: ")
    params['sub_domain'] = params['sub_domain'] \
            or args.subdomain or raw_input("Subomain: ") or "@"
    
    #Login info
    params['login_email'] = params['login_email'] \
            or args.username or raw_input("E-mail: ")
    params['login_password'] = params['login_password'] \
            or args.password or getpass()
    
    #domain and record id
    params['domain_id'] = params['domain_id'] \
            or args.domain_id or get_domain_id(domain)
    params['record_id'] = params['record_id']  \
            or args.record_id or get_record_id()
    
    print params
    if args.interface:
        getip = lambda : get_if_ip(args.interface)
    else:
        getip = lambda : get_public_ip()

    if DEBUG:
        print "domain_name: ", params['sub_domain']+'.'+domain
        print "domain id:", params['domain_id']
        print "record id:", params['record_id']
        print "interval: ", interval
    
    current_ip = get_current_ip()
    while True:
        try:
            ip = getip()
            if DEBUG: print "ip:", ip
            if DEBUG: print "record ip:", current_ip
            if current_ip != ip:
                if ddns(ip):
                    current_ip = ip
        except Exception, e:
            print e
            pass
        time.sleep(interval)

以后可能会常更新,新的代码会放在 github 上。

另有一份增强版,可以读却配置文件,支持更新多个域名,当然代码行数也多了… 放在 这里

要求是python2.7,python2.7以下版本(如在centos上)需要单独安装argparse模块

参数说明: ./dnspod.py -h

  -I INTERVAL, --interval INTERVAL
                        心跳时间,默认为5分钟
  -i INTERFACE, --interface INTERFACE
                        指定网络接口,不指定则会请求公共IP
  -u USERNAME, --username USERNAME
                        登陆的email,不指定则交互输入
  -p PASSWORD, --password PASSWORD
                        密码,不指定则交互输入
  -d DOMAIN, --domain DOMAIN
                        主域名,不指定则交互输入
  -s SUBDOMAIN, -r SUBDOMAIN, --subdomain SUBDOMAIN
                        子域,不指定则交互输入
  --domain-id DOMAIN_ID
                        主域ID,可选
  --record-id RECORD_ID
                        子域ID,由于一个子域可以有多条记录,这种情况必须指定,否则可选
  --debug               显示debug信息

例如: ./dnspod.py –debug -d bigeagle.me -s home -u xxx@gmail.com

则会更新home.bigeagle.me的记录