Implement updating of DuckDNS
This commit is contained in:
parent
552454cf6e
commit
115eeec47d
9 changed files with 128 additions and 39 deletions
|
@ -1,7 +1,9 @@
|
||||||
# ddnsclient
|
# ddnsclient
|
||||||
|
|
||||||
This application can be use to update dynamic hostnames. Currently it implements only the ddnss.de provider, but it
|
This application can be use to update dynamic hostnames. Currently it implements the following DynDNS providers:
|
||||||
is extensible and more providers can be added easily.
|
|
||||||
|
- ddnss.de
|
||||||
|
- duckdns.org
|
||||||
|
|
||||||
This project uses Quarkus.
|
This project uses Quarkus.
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() {
|
void run() {
|
||||||
log.trace("Updater running.");
|
log.info("Updater running.");
|
||||||
|
|
||||||
if (config.isEmpty()) {
|
if (config.isEmpty()) {
|
||||||
throw new IllegalStateException("Missing configuration");
|
throw new IllegalStateException("Missing configuration");
|
||||||
|
@ -58,7 +58,7 @@ public class Updater {
|
||||||
|
|
||||||
if (updateMap.containsKey(cfg.hostname())
|
if (updateMap.containsKey(cfg.hostname())
|
||||||
&& Duration.between(updateMap.get(cfg.hostname()), LocalDateTime.now()).toSeconds() < backoff.toSeconds()) {
|
&& Duration.between(updateMap.get(cfg.hostname()), LocalDateTime.now()).toSeconds() < backoff.toSeconds()) {
|
||||||
log.debug("Back-off period, skipping update.");
|
log.info("Back-off period, skipping update.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ public class Updater {
|
||||||
dynDns.update(cfg.hostname(), publicIps, new DyndnsAuth(null, null, cfg.token()));
|
dynDns.update(cfg.hostname(), publicIps, new DyndnsAuth(null, null, cfg.token()));
|
||||||
updateMap.put(cfg.hostname(), LocalDateTime.now());
|
updateMap.put(cfg.hostname(), LocalDateTime.now());
|
||||||
} else {
|
} else {
|
||||||
log.debugf("Hostname is up-to-date.", cfg.hostname());
|
log.infof("Hostname %s is up-to-date.", cfg.hostname());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,7 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
@ -29,26 +27,27 @@ public class Ddnss implements DynDns {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(String hostname, IPs currentIps, DyndnsAuth auth) {
|
public void update(String hostname, IPs currentIps, DyndnsAuth auth) {
|
||||||
|
log.tracef("Updating ddnss hostname %s", hostname);
|
||||||
String updateUrl = MessageFormat.format("https://ddnss.de/upd.php?key={0}&host={1}&ip={2}&ip6={3}",
|
|
||||||
auth.token(), hostname, currentIps.v4(), currentIps.v6());
|
|
||||||
|
|
||||||
try (HttpClient http = httpClientFactory.create()) {
|
try (HttpClient http = httpClientFactory.create()) {
|
||||||
|
String updateUrl = getUpdateUrl(hostname, currentIps, auth.token());
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpResponse<String> response = http.send(getRequest(updateUrl), HttpResponse.BodyHandlers.ofString());
|
||||||
.header("User-Agent", "ddns-client")
|
|
||||||
.GET()
|
|
||||||
.uri(URI.create(updateUrl))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (response.statusCode() != 200) {
|
if (response.statusCode() != 200) {
|
||||||
log.errorf("Couldn't update hostname %s." + hostname);
|
log.errorf("Couldn't update hostname %s." + hostname);
|
||||||
}
|
}
|
||||||
log.info("Hostname updated.");
|
log.infof("Hostname %s updated.", hostname);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getUpdateUrl(String hostname, IPs currentIps, String token) {
|
||||||
|
if (currentIps.v4().isPresent() && currentIps.v6().isPresent()) {
|
||||||
|
return MessageFormat.format("https://ddnss.de/upd.php?key={0}&host={1}&ip={2}&ip6={3}", token, hostname, currentIps.v4().get(), currentIps.v6().get());
|
||||||
|
} else if (currentIps.v4().isPresent()) {
|
||||||
|
return MessageFormat.format("https://ddnss.de/upd.php?key={0}&host={1}&ip={2}", token, hostname, currentIps.v4());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("No ips to update!");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
56
src/main/java/de/rpr/ddnsclient/dyndns/DuckDNS.java
Normal file
56
src/main/java/de/rpr/ddnsclient/dyndns/DuckDNS.java
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package de.rpr.ddnsclient.dyndns;
|
||||||
|
|
||||||
|
import de.rpr.ddnsclient.model.DyndnsAuth;
|
||||||
|
import de.rpr.ddnsclient.model.IPs;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class DuckDNS implements DynDns {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(DuckDNS.class);
|
||||||
|
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
|
public DuckDNS(HttpClientFactory httpClientFactory) {
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "duckdns";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(String hostname, IPs currentIps, DyndnsAuth auth) {
|
||||||
|
log.tracef("Updating duckdns hostname %s", hostname);
|
||||||
|
|
||||||
|
try (HttpClient http = httpClientFactory.create()) {
|
||||||
|
String updateUrl = getUpdateUrl(hostname, currentIps, auth.token());
|
||||||
|
HttpResponse<String> response = http.send(getRequest(updateUrl), HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
log.errorf("Couldn't update hostname %s." + hostname);
|
||||||
|
} else if (!response.body().equals("OK")) {
|
||||||
|
throw new RuntimeException("Couldn't update hostname!");
|
||||||
|
}
|
||||||
|
log.infof("Hostname %s updated.", hostname);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUpdateUrl(String hostname, IPs currentIps, String token) {
|
||||||
|
if (currentIps.v4().isPresent() && currentIps.v6().isPresent()) {
|
||||||
|
return MessageFormat.format("https://www.duckdns.org/update?domains={0}&token={1}&ip={2}&ipv6={3}", hostname, token, currentIps.v4().get(), currentIps.v6().get());
|
||||||
|
} else if (currentIps.v4().isPresent()) {
|
||||||
|
return MessageFormat.format("https://www.duckdns.org/update?domains={0}&token={1}&ip={2}", hostname, token, currentIps.v4().get());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("No ips to update!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,9 +3,20 @@ package de.rpr.ddnsclient.dyndns;
|
||||||
import de.rpr.ddnsclient.model.DyndnsAuth;
|
import de.rpr.ddnsclient.model.DyndnsAuth;
|
||||||
import de.rpr.ddnsclient.model.IPs;
|
import de.rpr.ddnsclient.model.IPs;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
|
||||||
public interface DynDns {
|
public interface DynDns {
|
||||||
|
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
void update(String hostname, IPs currentIps, DyndnsAuth auth);
|
void update(String hostname, IPs currentIps, DyndnsAuth auth);
|
||||||
|
|
||||||
|
default HttpRequest getRequest(String updateUrl) {
|
||||||
|
return HttpRequest.newBuilder()
|
||||||
|
.header("User-Agent", "ddns-client")
|
||||||
|
.GET()
|
||||||
|
.uri(URI.create(updateUrl))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package de.rpr.ddnsclient.lookup;
|
||||||
import de.rpr.ddnsclient.model.IPs;
|
import de.rpr.ddnsclient.model.IPs;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.xbill.DNS.Record;
|
import org.xbill.DNS.Record;
|
||||||
import org.xbill.DNS.*;
|
import org.xbill.DNS.*;
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ import java.util.Optional;
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class DnsJava implements DnsResolver {
|
public class DnsJava implements DnsResolver {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(DnsJava.class);
|
||||||
|
|
||||||
private final String resolverAddress;
|
private final String resolverAddress;
|
||||||
|
|
||||||
public DnsJava(@ConfigProperty(name = "ddnsclient.dns.resolver", defaultValue = "9.9.9.9") String resolverAddress) {
|
public DnsJava(@ConfigProperty(name = "ddnsclient.dns.resolver", defaultValue = "9.9.9.9") String resolverAddress) {
|
||||||
|
@ -19,19 +22,24 @@ public class DnsJava implements DnsResolver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPs resolve(String hostname) {
|
public IPs resolve(String hostname) {
|
||||||
String v4 = getIp(hostname, Type.A).orElseThrow(() -> new RuntimeException("Missing A Record"));
|
Optional<String> v4 = getIp(hostname, RecordType.A);
|
||||||
String v6 = getIp(hostname, Type.AAAA).orElseThrow(() -> new RuntimeException("Missing AAAA Record"));
|
Optional<String> v6 = getIp(hostname, RecordType.AAAA);
|
||||||
return new IPs(v4, v6);
|
return new IPs(v4, v6);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> getIp(String hostname, int type) {
|
private Optional<String> getIp(String hostname, RecordType type) {
|
||||||
try {
|
try {
|
||||||
SimpleResolver resolver = new SimpleResolver(resolverAddress);
|
SimpleResolver resolver = new SimpleResolver(resolverAddress);
|
||||||
|
|
||||||
Lookup lookup = new Lookup(hostname, type);
|
Lookup lookup = new Lookup(hostname, type.value);
|
||||||
lookup.setResolver(resolver);
|
lookup.setResolver(resolver);
|
||||||
|
|
||||||
Record[] records = lookup.run();
|
Record[] records = lookup.run();
|
||||||
|
if (records == null || records.length == 0) {
|
||||||
|
log.infof("No record for type %s found", type.name());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
for (Record record : records) {
|
for (Record record : records) {
|
||||||
if (record instanceof ARecord) {
|
if (record instanceof ARecord) {
|
||||||
return Optional.of(((ARecord) record).getAddress().getHostAddress());
|
return Optional.of(((ARecord) record).getAddress().getHostAddress());
|
||||||
|
@ -45,4 +53,15 @@ public class DnsJava implements DnsResolver {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum RecordType {
|
||||||
|
A(1),
|
||||||
|
AAAA(28);
|
||||||
|
|
||||||
|
private final int value; // org.bill.DNS.Type.AAAA
|
||||||
|
|
||||||
|
RecordType(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,7 @@ public class PublicIpLookup {
|
||||||
private final CurlProcessFactory curlProcessFactory;
|
private final CurlProcessFactory curlProcessFactory;
|
||||||
private final Optional<String> configuredProvider;
|
private final Optional<String> configuredProvider;
|
||||||
|
|
||||||
PublicIpLookup(
|
PublicIpLookup(CurlProcessFactory curlProcessFactory, @ConfigProperty(name = "ddnsclient.ip-provider") Optional<String> configuredProvider) {
|
||||||
CurlProcessFactory curlProcessFactory,
|
|
||||||
@ConfigProperty(name = "ddnsclient.ip-provider") Optional<String> configuredProvider
|
|
||||||
) {
|
|
||||||
this.curlProcessFactory = curlProcessFactory;
|
this.curlProcessFactory = curlProcessFactory;
|
||||||
this.configuredProvider = configuredProvider;
|
this.configuredProvider = configuredProvider;
|
||||||
}
|
}
|
||||||
|
@ -35,8 +32,7 @@ public class PublicIpLookup {
|
||||||
configuredProvider.ifPresent(it -> {
|
configuredProvider.ifPresent(it -> {
|
||||||
log.tracef("Setting ip lookup provider %s", it);
|
log.tracef("Setting ip lookup provider %s", it);
|
||||||
provider = Optional.of(IpLookupProviders.get(it));
|
provider = Optional.of(IpLookupProviders.get(it));
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Unknow provider configured!", e);
|
throw new IllegalStateException("Unknow provider configured!", e);
|
||||||
}
|
}
|
||||||
|
@ -48,8 +44,8 @@ public class PublicIpLookup {
|
||||||
try {
|
try {
|
||||||
String provider = this.provider.map(it -> it.url).orElseGet(() -> IpLookupProviders.random().url);
|
String provider = this.provider.map(it -> it.url).orElseGet(() -> IpLookupProviders.random().url);
|
||||||
|
|
||||||
String v4 = getIp(provider, CurlProcessFactory.IpClass.V4);
|
Optional<String> v4 = getIp(provider, CurlProcessFactory.IpClass.V4);
|
||||||
String v6 = getIp(provider, CurlProcessFactory.IpClass.V6);
|
Optional<String> v6 = getIp(provider, CurlProcessFactory.IpClass.V6);
|
||||||
|
|
||||||
return new IPs(v4, v6);
|
return new IPs(v4, v6);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -57,7 +53,7 @@ public class PublicIpLookup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getIp(String provider, CurlProcessFactory.IpClass ipClass) {
|
private Optional<String> getIp(String provider, CurlProcessFactory.IpClass ipClass) {
|
||||||
try {
|
try {
|
||||||
Process curlProcess = curlProcessFactory.create(provider, ipClass).start();
|
Process curlProcess = curlProcessFactory.create(provider, ipClass).start();
|
||||||
curlProcess.waitFor();
|
curlProcess.waitFor();
|
||||||
|
@ -65,9 +61,10 @@ public class PublicIpLookup {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
String ip = reader.readLine();
|
String ip = reader.readLine();
|
||||||
log.tracef("Retrieved ip: %s", ip);
|
log.tracef("Retrieved ip: %s", ip);
|
||||||
return ip;
|
return Optional.of(ip);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
log.errorf("Couldn't lookup ip. Provider: %s, ipClass: %s", provider, ipClass.name());
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package de.rpr.ddnsclient.model;
|
package de.rpr.ddnsclient.model;
|
||||||
|
|
||||||
public record IPs(String v4, String v6) {
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record IPs(Optional<String> v4, Optional<String> v6) {
|
||||||
|
|
||||||
|
public IPs(String v4, String v6) {
|
||||||
|
this(Optional.ofNullable(v4), Optional.ofNullable(v6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class DnsJavaTest {
|
||||||
@Test
|
@Test
|
||||||
void test() {
|
void test() {
|
||||||
IPs result = new DnsJava("9.9.9.9").resolve("example.com");
|
IPs result = new DnsJava("9.9.9.9").resolve("example.com");
|
||||||
assertThat(result.v4()).isNotBlank();
|
assertThat(result.v4()).isPresent();
|
||||||
assertThat(result.v6()).isNotBlank();
|
assertThat(result.v6()).isPresent();
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue