IPV6 阿里DDNS
因为需要在家搭建一套环境,并且需要公网能访问。国内的ipv4的地址,各大运营商基本都不会分配ipv4地址(电信宽带好像有地方可以,但是听说很贵),而且是动态的,每过段时间就会改变。
发现移动宽带的公网ipv6地址是可以获取到的,但是也会动态刷新。想稳定访问就加上阿里的ddns的域名访问。
有时候电脑的mac地址在路由器注册上,公网会访问不了。我也不知道是什么原因。但是晚上凌晨4点或者5点时候定时重启路由器,基本上就没问题。
1.改光猫信息和设置路由器拨号
因为宽带接入家里基本是都是需要通过光猫拨号后,再接入路由器。这样就拿不到真实的公网ipv6地址,需要先将光猫中的设置改为桥接模式,再让路由器输入宽带账户和密码拨号。
改光猫和路由器这个步骤,确实很多坑,需要看网上的很多教程。光猫的型号和地区不一样,设置的方式也不一样,所以这一步就不详细介绍。
2.准备阿里云域名和获取阿里开发AccessKeyID和AccessKeySecret
2.1 准备域名
注册一个阿里云账号,购买域名,可以买个比较便宜的一级域名。购买完成后
购买以后需要看看dns服务器设置是否成功。阿里免费的ddns解析,修改ip后,大概10分钟左右生效。
然后进入左侧的 “域名解析” 菜单
主机记录是二级域名前缀,@表示是主域名
记录类型 A是ipv4地址 AAAA是ipv6地址
TTL 生效时间
2.2 获取阿里开发AccessKeyID和AccessKeySecret
3.开始脚本项目
3.1 引入阿里的sdk包
引入阿里现成的maven包,里面封装好的接口
<dependencies>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-alidns</artifactId>
<version>2.0.10</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.12</version>
</dependency>
</dependencies>
3.2 加载配置信息
域名,AccessKeyID和AccessKeySecret
初始化下配置信息
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Config {
//这读取的是json文件配置,也可以直接输入域名,AccessKeyID,和AccessKeySecret
public static String host="";
public static String AccessKeyID="";
public static String AccessKeySecret="";
public static void initConfig(){
if(StrUtil.isNotBlank(host) && StrUtil.isNotBlank(AccessKeyID) && StrUtil.isNotBlank(AccessKeySecret)){
LogUtil.logOut("host:"+host);
LogUtil.logOut("AccessKeyID:"+AccessKeyID);
LogUtil.logOut("AccessKeySecret:"+AccessKeySecret);
return;
}
String jarPath= System.getProperty("user.dir");
System.out.println(jarPath);
File file=new File(jarPath+File.separator+"config.json");
if(!file.exists()){
LogUtil.logOut("initConfig config.json文件不存在");
return;
}
JSONObject json= JSONUtil.readJSONObject(file, StandardCharsets.UTF_8);
String host=json.getStr("host");
String AccessKeyID=json.getStr("AccessKeyID");
String AccessKeySecret=json.getStr("AccessKeySecret");
if(StrUtil.isBlank(host) || StrUtil.isBlank(AccessKeyID) || StrUtil.isBlank(AccessKeySecret)){
LogUtil.logOut("参数配置出现问题!!!!!!!!");
throw new RuntimeException("参数配置出现问题!!!!!!!!");
}
Config.host=host;
Config.AccessKeyID=AccessKeyID;
Config.AccessKeySecret=AccessKeySecret;
}
}
3.3 获取服务器ipv6地址
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @author liu
* @time 2021/11/1
* @description
*/
public class IPv6 {
private static final CopyOnWriteArrayList<String> ipv6s = new CopyOnWriteArrayList<>();
//随机数
static Random random = new Random();
static {
// 创建定时器任务
Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(() -> refreshIps(),1,60,TimeUnit.SECONDS);
}
private static synchronized void refreshIps() {
LogUtil.logOut("开始刷新ip");
try {
List<String> getIpv6s = getLocalIPv6Address();
if (getIpv6s.isEmpty()) {
return;
}
List<String> temp=new ArrayList<>();
List<String> finalTemp = temp;
getIpv6s.forEach(ip -> {
try {
if (pingTest(ip)) {
finalTemp.add(ip);
}
}catch (Exception e){
e.printStackTrace();
}
});
ipv6s.clear();
temp= temp.stream().distinct().collect(Collectors.toList());
ipv6s.addAll(temp);
if (ipv6s.isEmpty()) {
ipv6s.addAll(getIpv6s);
}
} catch (Exception e) {
e.printStackTrace();
}
LogUtil.logOut("结束刷新ip");
}
public static String getNextId(){
if(ipv6s.isEmpty()){
refreshIps();
}
if(ipv6s.isEmpty()){
return "";
}
int i2 = random.nextInt(1000);
int flag=i2%ipv6s.size();
return ipv6s.get(flag);
}
public static List<String> getLocalIPv6Address() throws SocketException {
InetAddress inetAddress = null;
List<String> ipv6s = new ArrayList<>();
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
Enumeration<InetAddress> inetAds = networkInterfaces.nextElement().getInetAddresses();
while (inetAds.hasMoreElements()) {
inetAddress = inetAds.nextElement();
//检查此地址是否是IPv6地址以及是否是保留地址
if (inetAddress instanceof Inet6Address && !isReservedAddr(inetAddress)) {
String ipAddr = inetAddress.getHostAddress();
//过滤网卡
int index = ipAddr.indexOf('%');
if (index > 0) {
ipAddr = ipAddr.substring(0, index);
}
ipv6s.add(ipAddr);
}
}
}
LogUtil.log_print("ip数据",ipv6s);
return ipv6s;
}
private static boolean isReservedAddr(InetAddress inetAddr) {
if (inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress() || inetAddr.isLoopbackAddress()) {
return true;
}
return false;
}
public static boolean ping(String ipAddress, int pingTimes) {
BufferedReader in = null;
// 将要执行的ping命令,此命令是windows格式的命令
Runtime r = Runtime.getRuntime();
//String pingCommand = "ping " + ipAddress + " -n " + pingTimes + " -w " + timeOut;
String pingCommand = "ping6 " + ipAddress + " -c " + pingTimes;
try { // 执行命令并获取输出
LogUtil.logOut(pingCommand);
Process p = r.exec(pingCommand);
if (p == null) {
return false;
}
// 逐行检查输出,计算类似出现=23ms TTL=62字样的次数
in = new BufferedReader(new InputStreamReader(p.getInputStream()));
int connectedCount = 0;
String line = null;
while ((line = in.readLine()) != null) {
connectedCount += getCheckResult(line);
} // 如果出现类似=23ms TTL=62这样的字样,出现的次数=测试次数则返回真
return connectedCount >= pingTimes;
} catch (Exception ex) {
ex.printStackTrace(); // 出现异常则返回假
return false;
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 用外网测试网站来测试https://ipw.cn/api/ping/ipv6
*/
private static boolean pingTest(String ipaddr) {
String pingAddr = String.format("https://ipw.cn/api/ping/ipv6/%s/4/all", ipaddr);
String reStr = HttpURLConnectionUtil.doGet(pingAddr);
JSONObject json = JSONUtil.parseObj(reStr);
JSONArray jsonArray = json.getJSONArray("pingResultDetail");
for (Object jsonr : jsonArray) {
JSONObject jsonResult = (JSONObject) jsonr;
Boolean result = jsonResult.getBool("result");
if (result) {
return true;
}
}
return false;
}
/**
* 若line含有=18ms TTL=16字样,说明已经ping通,返回1,否則返回0.
*/
private static int getCheckResult(String line) {
LogUtil.logOut("控制台输出的结果为:" + line);
String[] lines = line.split("=");
String lessStr = lines[lines.length - 1].split(" ")[0];
try {
if (line.contains("Unreachable")) {
return 0;
}
if (line.contains("unreachable")) {
return 0;
}
if (Double.valueOf(lessStr) > 0) {
return 1;
}
} catch (Exception e) {
return 0;
}
return 0;
}
}
3.4 main启动类
import cn.hutool.core.util.StrUtil;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.alidns.model.v20150109.*;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import java.io.IOException;
import java.net.SocketException;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DDNS {
/**
* 获取主域名的所有解析记录列表
*/
private DescribeSubDomainRecordsResponse describeSubDomainRecords(DescribeSubDomainRecordsRequest request, IAcsClient client) {
try {
// 调用SDK发送请求
return client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
// 发生调用错误,抛出运行时异常
throw new RuntimeException();
}
}
/**
* 修改解析记录
*/
private UpdateDomainRecordResponse updateDomainRecord(UpdateDomainRecordRequest request, IAcsClient client) {
try {
// 调用SDK发送请求
return client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
// 发生调用错误,抛出运行时异常
throw new RuntimeException();
}
}
/**
* 修改解析记录
*/
private AddDomainRecordResponse addDomainRecord(AddDomainRecordRequest request, IAcsClient client) {
try {
// 调用SDK发送请求
return client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
// 发生调用错误,抛出运行时异常
throw new RuntimeException();
}
}
public static void main(String[] args) {
Config.initConfig();
// 设置鉴权参数,初始化客户端
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou",// 地域ID
Config.AccessKeyID,// 您的AccessKey ID
Config.AccessKeySecret);// 您的AccessKey Secret
IAcsClient client = new DefaultAcsClient(profile);
// 创建定时器任务
Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(() -> {
try {
ddnsOp(client);
} catch (Exception e) {
e.printStackTrace();
LogUtil.logOut(e.getMessage());
}
}, 1, 300, TimeUnit.SECONDS);
}
private static void ddnsOp(IAcsClient client) {
DDNS ddns = new DDNS();
//查询指定二级域名的最新解析记录
DescribeSubDomainRecordsRequest describeSubDomainRecordsRequest = new DescribeSubDomainRecordsRequest();
describeSubDomainRecordsRequest.setSubDomain(Config.host);
DescribeSubDomainRecordsResponse describeSubDomainRecordsResponse = ddns.describeSubDomainRecords(describeSubDomainRecordsRequest, client);
LogUtil.log_print("describeSubDomainRecords", describeSubDomainRecordsResponse);
List<DescribeSubDomainRecordsResponse.Record> domainRecords = describeSubDomainRecordsResponse.getDomainRecords();
//最新的一条解析记录
if (domainRecords.size() != 0) {
DescribeSubDomainRecordsResponse.Record record = domainRecords.get(0);
// 记录ID
String recordId = record.getRecordId();
// 记录值
String recordsValue = record.getValue();
// 当前主机公网IP
String currentHostIP = IPv6.getNextId();
if (StrUtil.isBlank(currentHostIP)) {
LogUtil.logOut("----------无法获取到主机ip-----------");
return;
}
LogUtil.logOut("-------------------------------当前主机公网IP为:" + currentHostIP + "-------------------------------");
if (!currentHostIP.equals(recordsValue)) {
LogUtil.logOut("-------------------------------当前主机公网IP不一致,开始修改-------------------------------");
// 修改解析记录
UpdateDomainRecordRequest updateDomainRecordRequest = new UpdateDomainRecordRequest();
// 主机记录
updateDomainRecordRequest.setRR("@");
// 记录ID
updateDomainRecordRequest.setRecordId(recordId);
// 将主机记录值改为当前主机IP
updateDomainRecordRequest.setValue(currentHostIP);
// 解析记录类型
updateDomainRecordRequest.setType("AAAA");
UpdateDomainRecordResponse updateDomainRecordResponse = ddns.updateDomainRecord(updateDomainRecordRequest, client);
LogUtil.log_print("updateDomainRecord", updateDomainRecordResponse);
LogUtil.logOut("-------------------------------修改结束-------------------------------");
}else {
LogUtil.logOut("-------------------------------当前主机公网IP一致,不需要修改-------------------------------");
}
} else {
// 当前主机公网IP
String currentHostIP = IPv6.getNextId();
// 当前主机公网IP
if (StrUtil.isBlank(currentHostIP)) {
LogUtil.logOut("----------无法获取到主机ip-----------");
return;
}
LogUtil.logOut("-------------------------------当前主机公网IP为:" + currentHostIP + "-------------------------------");
AddDomainRecordRequest addDomainRecordRequest = new AddDomainRecordRequest();
addDomainRecordRequest.setDomainName(Config.host);
addDomainRecordRequest.setRR("@");
// 将主机记录值改为当前主机IP
addDomainRecordRequest.setValue(currentHostIP);
// 解析记录类型
addDomainRecordRequest.setType("AAAA");
AddDomainRecordResponse updateDomainRecordResponse = ddns.addDomainRecord(addDomainRecordRequest, client);
LogUtil.log_print("updateDomainRecord", updateDomainRecordResponse);
}
}
}
3.5 config.json配置文件
***这个配置文件需要和jar包同级目录***
{
"host":"ljXXXX.top",
"AccessKeyID":"LTAI5tPXXXXXXX",
"AccessKeySecret":"FhkXXXXXX"
}
3.6 ipv6线上测试工具
import com.sun.istack.Nullable;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Author: liu
* @Description: http工具
* @Date: 2023/3/3 15:20
*/
public class HttpURLConnectionUtil {
/**
* http get请求
* @param httpUrl 链接
* @return 响应数据
*/
public static String doGet(String httpUrl){
//链接
HttpURLConnection connection=null;
InputStream is=null;
BufferedReader br = null;
StringBuffer result=new StringBuffer();
try {
//创建连接
URL url=new URL(httpUrl);
connection= (HttpURLConnection) url.openConnection();
//设置请求方式
connection.setRequestMethod("GET");
//设置连接超时时间
connection.setConnectTimeout(3000);
//设置读取超时时间
connection.setReadTimeout(3000);
//开始连接
connection.connect();
//获取响应数据
if(connection.getResponseCode()==200){
//获取返回的数据
is=connection.getInputStream();
if(is!=null){
br=new BufferedReader(new InputStreamReader(is,"UTF-8"));
String temp = null;
while ((temp=br.readLine())!=null){
result.append(temp);
}
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
connection.disconnect();// 关闭远程连接
}
return result.toString();
}
/**
* post请求
* @param httpUrl 链接
* @param param 参数
* @return
*/
public static String doPost(String httpUrl, @Nullable String param) {
StringBuffer result=new StringBuffer();
//连接
HttpURLConnection connection=null;
OutputStream os=null;
InputStream is=null;
BufferedReader br=null;
try {
//创建连接对象
URL url=new URL(httpUrl);
//创建连接
connection= (HttpURLConnection) url.openConnection();
//设置请求方法
connection.setRequestMethod("POST");
//设置连接超时时间
connection.setConnectTimeout(15000);
//设置读取超时时间
connection.setReadTimeout(15000);
//设置是否可读取
connection.setDoOutput(true);
//设置响应是否可读取
connection.setDoInput(true);
//设置参数类型
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//拼装参数
if(param!=null&&!param.equals("")){
//设置参数
os=connection.getOutputStream();
//拼装参数
os.write(param.getBytes("UTF-8"));
}
//设置权限
//设置请求头等
//开启连接
//connection.connect();
//读取响应
if(connection.getResponseCode()==200){
is=connection.getInputStream();
if(is!=null){
br=new BufferedReader(new InputStreamReader(is,"UTF-8"));
String temp=null;
if((temp=br.readLine())!=null){
result.append(temp);
}
}
}
//关闭连接
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭连接
connection.disconnect();
}
return result.toString();
}
}
3.7 日志输出
import com.google.gson.Gson;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author fancy
*/
public class LogUtil {
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
public static void logOut(Object info){
String threadName=Thread.currentThread().getName();
System.out.println(dtf.format(LocalDateTime.now())+"--线程名"+threadName+":::"+info);
}
public static void log_print(String functionName, Object result) {
Gson gson = new Gson();
LogUtil.logOut("-------------------------------" + functionName + "-------------------------------");
LogUtil.logOut(gson.toJson(result));
}
}
3.7 使用脚本
aliDDns.sh 启动
#!/bin/bash
APP_NAME=aliDDns-1.0-SNAPSHOT.jar
#使用说明,用来提示输入参数
usage() {
echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#启动方法
start(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
#启动时设置并发垃圾收集器
nohup java -Xms64m -Xmx64m -XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=32m -cp ./target/$APP_NAME DDNS > log.file 2>&1 &
echo "${APP_NAME} start success"
fi
}
#停止方法
# shellcheck disable=SC2120
stop(){
is_exist
if [ $? -eq "0" ]; then
PROCESS=`ps -ef|grep $APP_NAME|grep -v grep|grep -v PPID|awk '{ print $2}'`
for i in $PROCESS
do
echo "Kill the $1 process [ $i ]"
kill -9 $i
done
else
echo "${APP_NAME} is not running"
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}
#重启
restart(){
stop
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac
总结
如果不是做java研发,这个脚本跑起来有点难。这个是打包好的
打包好的zip文件 ,所有文件放在同级目录,或者改下shell脚本中的路径也行
用过一段时间后发现还是有问题,又加了检测方式和更新ip方式,建议把阿里的域名解析通知给关了,不然会频繁发送邮件或者通知。
如果没有积分可留言
代码地址源码 代码有更新,文章的代码已经过时
声明:该项目只是做个人学习使用,并未将域名暴露他人。网友其他行为与本人无关!!!