家里放了一台旧 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 函数。