编写 java 程序,为家用电脑 ipv6 自动更新 goddy dns 记录(ddns)

2021-11-25 17:21

家里放了一台旧 acer 笔记本电脑,外挂几个硬盘盒,插上几个硬盘,组成硬盘盒。 因笔记本电脑的耗电较小,硬盘盒有自动休眠省电模式,所以长期开机。此笔记本电脑,使用家庭的移动宽带,会自动分配 IPv6 , 可远程连接。 之前,自动分配 IPv6,很长时间不变。当然,我不可能去记这个 IPv6,所以找个 DNS 服务器,记在 AAAA 记录里。 最近好像不停地变化,这就麻烦了。用 cn.bing.com 网上搜索一番, 找到有脚本可自动更新 goddy dns 记录(ddns)。恰好我购买了一个goddy 的域名,附送 DNS 管理,也有免费的 DDNS 编程接口。脚本语言我不太熟悉,于是就改成用 java 重写了一次。

程序分两部分:

a. 主控 main 类 UpdateDdnsApp,负责反复循环,每间隔 5 秒,检查 IP 地址是否变化。

b. 业务操作类 UpdateSrv, 负责单次循环中的业务(更新 DDNS)。

 

代码比较简单,直接贴上,以下是 UpdateDdnsApp.java :


package update_godaddy_ddns;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//public class UpdateDdns {
public class UpdateDdnsApp {

	public static void main(String[] args) {
		Logger log = LoggerFactory.getLogger(UpdateDdnsApp.class);
		log.info("UpdateDdnsApp main begin...");
		String domain = args[0]; // "mydomain.com" # your domain
		// String typeMulti = args[1]; // ="A" # Record type A, CNAME, MX, etc.
		// String name = args[2]; // "sip" # name of record to update

		String typeToHostNameMulti = args[1]; // sample: AAAA/issues,AAAA/issues2

		String ttl = args[2]; // "3600" # Time to Live min value 600
		String port = args[3]; // "1" # Required port, Min value 1
		String weight = args[4]; // "1" # Required weight, Min value 1
		String key = args[5]; // "abc" # key for godaddy developer API
		String secret = args[6]; // "efg" # secret for godaddy developer API

		// String[] typeArrr = typeMulti.split("/");
		String[] typeToHostNameArr = typeToHostNameMulti.split(",");
		UpdateSrv srv = new UpdateSrv();

		// String lastUpdatedIpToDnsServer;
		// String lastIpUpdatedToDnsServer = null;
		Map dnsRecordTypeToLastIpUpdatedToDnsServerMap = new HashMap();
		// java.util.LinkedList ipActiveInOrder = new LinkedList();

		LinkedHashMap ipToActiveTimeInOrder = new LinkedHashMap();

		log.info("start work loop");
		FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
		while (true) {
			try {
				Thread.sleep(5000);
				ArrayList ipListForInternet0 = new ArrayList();

				// getFromLocalMachine(ipListForInternet);
				srv.getInternetIpv6FromLocalMachine(ipListForInternet0);
				if (ipListForInternet0.isEmpty()) {
					log.warn("cannot find ip for internet");
					// return;
					continue;
				}

				// srv.addActiveIpToOrderedList(ipListForInternet0, ipActiveInOrder);
				srv.addActiveIpToOrderedList(ipListForInternet0, ipToActiveTimeInOrder);

				// for (String type : typeArrr) {
				for (String typeToHostName : typeToHostNameArr) {
					log.info("typeToHostName:" + typeToHostName);
					// sample: AAAA/issues,AAAA/issues2
					String[] typeAndHostNameArr = typeToHostName.split("/");
					String dnsRecordType = typeAndHostNameArr[0];
					String hostName = typeAndHostNameArr[1];

					String lastIpUpdatedToDnsServer = null;
					if (dnsRecordTypeToLastIpUpdatedToDnsServerMap.containsKey(dnsRecordType)) {
						lastIpUpdatedToDnsServer = dnsRecordTypeToLastIpUpdatedToDnsServerMap.get(dnsRecordType);
					}

					// srv.doUpdate(domain, type, name, ttl, port, weight, key, secret);
					// if (this.lastUpdatedIpToDnsServer == null) {
					if (lastIpUpdatedToDnsServer == null) {
						// String ipFromDns = srv.getIpFromDnsServer(domain, type, name, ttl, port,
						// weight, key, secret);
						String ipFromDns = srv.getIpFromDnsServer(domain, dnsRecordType, hostName, ttl, port, weight,
								key, secret);
						// this.lastUpdatedIpToDnsServer = ipFromDns;
						lastIpUpdatedToDnsServer = ipFromDns;
					}

					// check if already send ip of local machine public ip(in used) to DDNS server
					// if (StringUtils.isNotEmpty(this.lastUpdatedIpToDnsServer)) {
					if (StringUtils.isNotEmpty(lastIpUpdatedToDnsServer)) {
//						for (InetAddress ia : ipListForInternet) {
//							// String ip = ia.getHostAddress();
//							String ip = srv.getIp(ia);
//							// if (ip.equalsIgnoreCase(this.lastUpdatedIpToDnsServer)) {
//							if (ip.equalsIgnoreCase(lastIpUpdatedToDnsServer)) {
//								log.info("local machine ip not changed,no need to update:" + ip);
//								return;
//							}
//						}
						// if (ipActiveInOrder.contains(lastIpUpdatedToDnsServer)) {
						if (ipToActiveTimeInOrder.containsKey(lastIpUpdatedToDnsServer)) {
							Date d = ipToActiveTimeInOrder.get(lastIpUpdatedToDnsServer);

							log.info("local machine ip not changed,no need to update:" + lastIpUpdatedToDnsServer
									+ ",first time got ip:" + fdf.format(d));
							// return;
							if (!dnsRecordTypeToLastIpUpdatedToDnsServerMap.containsKey(dnsRecordType)) {
								dnsRecordTypeToLastIpUpdatedToDnsServerMap.put(dnsRecordType, lastIpUpdatedToDnsServer);
							}
							continue;
						}
					}

					// use the ip for active-time very long
					// String newIp = srv.pickUpOneIpV6(ipListForInternet);
					// String newIp = ipActiveInOrder.getFirst();
					String newIp = null;
					for (Map.Entry ent : ipToActiveTimeInOrder.entrySet()) {
						newIp = ent.getKey();
						break;
					}

					if (StringUtils.isEmpty(newIp)) {
						log.warn("cannot find ipv6 for internet,2");
						// return;
						continue;
					}

					log.info("try update for newIp:" + newIp + ",dnsRecordType:" + dnsRecordType);
					// srv.tryUpdateIpForDns(newIp, domain, dnsRecordType, name, ttl, port, weight,
					// key, secret);
					srv.tryUpdateIpForDns(newIp, domain, dnsRecordType, hostName, ttl, port, weight, key, secret);
					log.info("after update for newIp:" + newIp + ",dnsRecordType:" + dnsRecordType);

					// if no error
					// this.lastUpdatedIpToDnsServer = newIp;
					lastIpUpdatedToDnsServer = newIp;
					log.info("saved new ip:" + newIp + ",dnsRecordType:" + dnsRecordType);
					dnsRecordTypeToLastIpUpdatedToDnsServerMap.put(dnsRecordType, newIp);
				}

			} catch (Exception err) {
				// err.printStackTrace();
				log.error(err.getMessage(), err);
			}
		}
		// log.info("main end");
	}

}
		
		

 

以下是 UpdateSrv.java:


package update_godaddy_ddns;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

public class UpdateSrv {
	// private String lastUpdatedIpToDnsServer;

//	public void doUpdate(String domain, String type, String name, String ttl, String port, String weight, String key,
//			String secret) throws ClientProtocolException, IOException {
//		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
//		log.info("doUpdate begin...");
//
//		if (this.lastUpdatedIpToDnsServer == null) {
//			String ipFromDns = getIpFromDnsServer(domain, type, name, ttl, port, weight, key, secret);
//			this.lastUpdatedIpToDnsServer = ipFromDns;
//		}
//
//		ArrayList ipListForInternet = new ArrayList();
//
//		// getFromLocalMachine(ipListForInternet);
//		getInternetIpsFromLocalMachine(ipListForInternet);
//
//		if (ipListForInternet.isEmpty()) {
//			log.warn("cannot find ipv6 for internet");
//			return;
//		}
//
//		// check if already send ip of local machine public ip(in used) to DDNS server
//		if (StringUtils.isNotEmpty(this.lastUpdatedIpToDnsServer)) {
//			for (InetAddress ia : ipListForInternet) {
//				// String ip = ia.getHostAddress();
//				String ip = getIp(ia);
//				if (ip.equalsIgnoreCase(this.lastUpdatedIpToDnsServer)) {
//					log.info("local machine ip not changed,no need to update:" + ip);
//					return;
//				}
//			}
//		}
//
//		String newIp = pickUpOneIpV6(ipListForInternet);
//		if (StringUtils.isEmpty(newIp)) {
//			log.warn("cannot find ipv6 for internet,2");
//			return;
//		}
//
//		log.info("try update for newIp:" + newIp);
//		tryUpdateIpForDns(newIp, domain, type, name, ttl, port, weight, key, secret);
//		log.info("after update for newIp:" + newIp);
//
//		// if no error
//		this.lastUpdatedIpToDnsServer = newIp;
//
//	}

	public void tryUpdateIpForDns(String newIp, String domain, String type, String name, String ttl, String port,
			String weight, String key, String secret) throws ClientProtocolException, IOException {
		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
		log.info("tryUpdateIpForDns begin..." + newIp);

		// 1.获得一个httpclient对象
		try (CloseableHttpClient httpclient = HttpClients.createDefault()) {

			// 2.生成一个 put 请求
			String url = "https://api.godaddy.com/v1/domains/" + domain + "/records/" + type + "/" + name;
			HttpPut httpput = new HttpPut(url);
			httpput.setHeader("Content-Type", "application/json");
			httpput.setHeader("Authorization", "sso-key " + key + ":" + secret);

			String strJson = "[{\"data\":\"" + newIp + "\",\"ttl\":" + ttl + "}]";
			StringEntity stringEntity = new StringEntity(strJson, "utf-8");
			httpput.setEntity(stringEntity);

			// 3.执行 put 请求并返回结果
			try (CloseableHttpResponse response = httpclient.execute(httpput)) {
				log.info("after httpclient.execute");

//				try {
				// 4.处理结果
				StatusLine sl = response.getStatusLine();
				log.info("getStatusLine:" + sl);
				// HTTP/1.1 401 Unauthorized
				if (sl.getStatusCode() == org.apache.http.HttpStatus.SC_OK) {
					log.info("updated success");
					StringBuilder sbBuffer = new StringBuilder();

					try (InputStream is = response.getEntity().getContent()) {
						try (InputStreamReader isr = new InputStreamReader(is)) {
							try (BufferedReader reader = new BufferedReader(isr)) {
								String line;
								while ((line = reader.readLine()) != null) {
									log.info("strResponse:" + line);
									sbBuffer.append(line);
								}
							}
						}
					}

					String strResponse = sbBuffer.toString();
					// should be empty
					log.info("strResponse:" + strResponse);
				} else {

				}
			}
		}
	}

	// private void getFromLocalMachine(ArrayList ipListForInternet) throws
	// SocketException {
	// public void getInternetIpsFromLocalMachine(ArrayList
	// ipListForInternet) throws SocketException {
	public void getInternetIpv6FromLocalMachine(ArrayList ipListForInternet) throws SocketException {
		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
		log.info("getInternetIpsFromLocalMachine begin...");

		// get ip-v6 only
		Enumeration ints = NetworkInterface.getNetworkInterfaces();
		while (ints.hasMoreElements()) {
			// each network card
			NetworkInterface it = ints.nextElement();

			Enumeration adds = it.getInetAddresses();
			while (adds.hasMoreElements()) {
				// each ip
				InetAddress ia = adds.nextElement();
				// String ip = ia.getHostAddress();
				String ip = getIp(ia);
//				if (ia instanceof Inet4Address) {
//					continue;
//				} else if (ia instanceof Inet6Address) {
				if (ia.isAnyLocalAddress()) {
					continue;
				} else if (ia.isLoopbackAddress()) {
					continue;
				} else if (ia.isLinkLocalAddress()) {
					continue;
				} else if (ia.isSiteLocalAddress()) {
					continue;
				} else if (!(ia instanceof Inet6Address)) {
					continue;
				}

				if (ip.startsWith("fe80:")) {
					continue;
				}
				// ipListForInternet.add(ip);
				ipListForInternet.add(ia);
//				} else {
//					// should not goes here.
//				}

			}
		}
	}

	public String getIpFromDnsServer(String domain, String type, String name, String ttl, String port, String weight,
			String key, String secret) throws ClientProtocolException, IOException {
		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
		log.info("getIpFromDns begin...");

		// 1.获得一个httpclient对象
		try (CloseableHttpClient httpclient = HttpClients.createDefault()) {

			// 2.生成一个get请求
			String url = "https://api.godaddy.com/v1/domains/" + domain + "/records/" + type + "/" + name;
			HttpGet httpget = new HttpGet(url);
			httpget.addHeader("Authorization", "sso-key " + key + ":" + secret);
			// 3.执行get请求并返回结果
			try (CloseableHttpResponse response = httpclient.execute(httpget)) {
				log.info("after httpclient.execute");

//				try {
				// 4.处理结果
				StatusLine sl = response.getStatusLine();
				log.info("getStatusLine:" + sl);
				// HTTP/1.1 401 Unauthorized
				if (sl.getStatusCode() == org.apache.http.HttpStatus.SC_OK) {
					StringBuilder sbBuffer = new StringBuilder();

					try (InputStream is = response.getEntity().getContent()) {
						try (InputStreamReader isr = new InputStreamReader(is)) {
							try (BufferedReader reader = new BufferedReader(isr)) {
								String line;
								while ((line = reader.readLine()) != null) {
									log.info("strResponse line:" + line);
									sbBuffer.append(line);
								}
							}
						}
					}

					String strResponse = sbBuffer.toString();
					// [{"data":"2409:8a1e:90ad:a1e0:dd40:8bcd:xxx","name":"abc-ipv6","ttl":1800,"type":"AAAA"}]
					log.info("strResponse:" + strResponse);

					// parse
					String ip = parseIpFromGodaddyResponse(strResponse);
					return ip;
				} else {

				}
			}
		}

		return null;
	}

	public String parseIpFromGodaddyResponse(String strResponse) {
		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
		log.info("parseIpFromGodaddyResponse begin..." + strResponse);

		// [{"data":"2409:8a1e:90ad:a1e0:dd40:8bcd:xxx","name":"abc-ipv6","ttl":1800,"type":"AAAA"}]
		// JSONObject obj = JSONObject.fromObject(strResponse);
		JSONArray arr = JSONArray.fromObject(strResponse);

		for (Object object : arr) {
			JSONObject obj = (JSONObject) object;

			// Set keys = obj.keySet();
			String name = (String) obj.get("name");
			Number ttl = (Number) obj.get("ttl");
			String type = (String) obj.get("type");
			String data = (String) obj.get("data");

			log.info("name:" + name + ",ttl:" + ttl + ",type:" + type + ",data:" + data);

			if (StringUtils.isNotEmpty(data)) {
				return data;
			}
		}
		// log.info(arr.toString());

		return null;
	}

//	// public String pickUpOneIpV6(Collection ipListForInternet) {
//	public String pickUpOneIp(Collection ipListForInternet) {
//		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
//		log.info("pickUpOneIp begin...");
//
//		for (InetAddress ia : ipListForInternet) {
////			if (ia instanceof Inet4Address) {
////				continue;
////			} else if (ia instanceof Inet6Address) {
//			String ip = getIp(ia);
//			return ip;
////			} else {
////				// should not goes here.
////			}
//		}
//		return null;
//	}

	public String getIp(InetAddress ia) {
		String ip = ia.getHostAddress();
		// 2409:891e:9340:xxx:xxx:xxx:f3c7:xxx%wlp4s0
		int pos = ip.lastIndexOf("%");
		if (pos > 0) {
			ip = ip.substring(0, pos);
		}
		return ip;
	}

	public void addActiveIpToOrderedList(Collection ipListForInternet,
			// LinkedList ipActiveInOrder
			LinkedHashMap ipToActiveTimeInOrder) {
		Logger log = LoggerFactory.getLogger(UpdateSrv.class);
		log.info("addActiveIpToOrderedList begin...");

		// step 1, remove non-active ip
		List oldIpListTobeRemove = new LinkedList();
		// for (String oldIp : ipActiveInOrder) {
		for (Map.Entry ent : ipToActiveTimeInOrder.entrySet()) {
			String oldIp = ent.getKey();

			boolean existed = false;
			for (InetAddress ia : ipListForInternet) {
				// String newIp = ia.getHostAddress();
				String newIp = getIp(ia);
				if (StringUtils.equalsIgnoreCase(newIp, oldIp)) {
					existed = true;
					break;
				}
			}
			if (!existed) {
				log.info("remove from list for non-active ip:" + oldIp);
				oldIpListTobeRemove.add(oldIp);
			}
		}
		for (String oldIp : oldIpListTobeRemove) {
			// ipActiveInOrder.remove(oldIp);
			ipToActiveTimeInOrder.remove(oldIp);
		}

		// step 2, add new-active ip
		for (InetAddress ia : ipListForInternet) {
			// String newIp = ia.getHostAddress();
			String newIp = getIp(ia);
			// if (!ipActiveInOrder.contains(newIp)) {
			if (!ipToActiveTimeInOrder.containsKey(newIp)) {
				log.info("add to list for active ip:" + newIp);
				// ipActiveInOrder.addLast(newIp);
				ipToActiveTimeInOrder.put(newIp, new Date());
			}
		}
	}
}

		

 

编译后,在 ubuntu/Debian 下,使用命令行运行:


export CLASSPATH=".:../bin:../lib/slf4j-api-1.7.25.jar:../lib/httpclient-4.5.2.jar:../lib/httpcore-4.4.4.jar:../lib/jcl-over-slf4j-1.7.25.jar:../lib/logback-classic-1.2.3.jar:../lib/logback-core-1.2.3.jar:../lib/json-lib-2.4-jdk15.jar:../lib/commons-lang-2.4.jar:../lib/commons-lang3-3.0.1.jar:../lib/ezmorph-1.0.6.jar:../lib/commons-collections-3.2.2.jar:../lib/commons-beanutils-1.9.3.jar"
java update_godaddy_ddns.UpdateDdnsApp some.com AAAA zg_xxx 1800 1 1 xxx yyy
		

如果是在 Windows 下,使用命令行运行:


set CLASSPATH=".:../bin:../lib/slf4j-api-1.7.25.jar:../lib/httpclient-4.5.2.jar:../lib/httpcore-4.4.4.jar:../lib/jcl-over-slf4j-1.7.25.jar:../lib/logback-classic-1.2.3.jar:../lib/logback-core-1.2.3.jar:../lib/json-lib-2.4-jdk15.jar:../lib/commons-lang-2.4.jar:../lib/commons-lang3-3.0.1.jar:../lib/ezmorph-1.0.6.jar:../lib/commons-collections-3.2.2.jar:../lib/commons-beanutils-1.9.3.jar"
java update_godaddy_ddns.UpdateDdnsApp some.com AAAA zg_xxx 1800 1 1 xxx yyy
		

命令行参数的含义,见 UpdateDdnsApp.java 中的 main 函数。

 

 

欢迎转载,转载请注明出处: http://www.zheguisoft.com/staff_blogs/jacklondon_chen/2021/auto_update_ddns_for_goddy_dns_with_java