aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-04-03 15:17:08 -0700
committerTrumeet <yuuta@yuuta.moe>2021-04-03 15:17:08 -0700
commitfb9fb86002178f9e8b4693d2cf50cacb55978424 (patch)
tree605d1efdcada4f81a0d1f0126d71199ce9b2a972
parent8b655e6058feaa4a00c6824400d57b3969d5e4db (diff)
downloaddn42peering-fb9fb86002178f9e8b4693d2cf50cacb55978424.tar
dn42peering-fb9fb86002178f9e8b4693d2cf50cacb55978424.tar.gz
dn42peering-fb9fb86002178f9e8b4693d2cf50cacb55978424.tar.bz2
dn42peering-fb9fb86002178f9e8b4693d2cf50cacb55978424.zip
feat(agent): use `ip` to operate WireGuard interfaces instead of wg-quick
Existing wg-quick services will be automatically removed. Manual inspection may be required.
-rw-r--r--agent/build.gradle2
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java3
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/AddrInfoItem.java96
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Address.java208
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IP.java216
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPException.java12
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPKernelException.java7
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPOptions.java82
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPSyntaxException.java7
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Link.java219
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Linkinfo.java19
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java4
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IPChange.java31
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java313
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardSyncConfChange.java51
-rw-r--r--agent/src/main/resources/wg.conf.ftlh11
-rw-r--r--rpc-common/src/main/java/moe/yuuta/dn42peering/RPC.java5
17 files changed, 1150 insertions, 136 deletions
diff --git a/agent/build.gradle b/agent/build.gradle
index ccd5471..67be7e2 100644
--- a/agent/build.gradle
+++ b/agent/build.gradle
@@ -34,6 +34,8 @@ dependencies {
annotationProcessor "io.vertx:vertx-service-proxy:${project.vertxVersion}"
compileOnly "io.vertx:vertx-codegen:${project.vertxVersion}"
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.2'
+
implementation project(':rpc-common')
}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java
index c1ad43d..4c4deb2 100644
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java
@@ -44,7 +44,8 @@ class AgentServiceImpl extends VertxAgentGrpc.AgentVertxImplBase {
}
return CompositeFuture.all(changes);
})
- .onComplete(res -> logger.info("Deployment finished. Detailed log can be traced above."))
+ .onSuccess(res -> logger.info("Deployment finished. Detailed log can be traced above."))
+ .onFailure(err -> logger.error("Deployment failed. Detailed log can be traced above.", err))
.compose(compositeFuture -> Future.succeededFuture(null));
}
}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/AddrInfoItem.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/AddrInfoItem.java
new file mode 100644
index 0000000..62017b2
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/AddrInfoItem.java
@@ -0,0 +1,96 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AddrInfoItem{
+
+ @JsonProperty("address")
+ private String address;
+
+ @JsonProperty("scope")
+ private String scope;
+
+ @JsonProperty("prefixlen")
+ private Integer prefixlen;
+
+ @JsonProperty("valid_life_time")
+ private Long validLifeTime;
+
+ @JsonProperty("family")
+ private String family;
+
+ @JsonProperty("preferred_life_time")
+ private Long preferredLifeTime;
+
+ @JsonProperty("local")
+ private String local;
+
+ @JsonProperty("label")
+ private String label;
+
+ public void setAddress(String address){
+ this.address = address;
+ }
+
+ public String getAddress(){
+ return address;
+ }
+
+ public void setScope(String scope){
+ this.scope = scope;
+ }
+
+ public String getScope(){
+ return scope;
+ }
+
+ public void setPrefixlen(Integer prefixlen){
+ this.prefixlen = prefixlen;
+ }
+
+ public Integer getPrefixlen(){
+ return prefixlen;
+ }
+
+ public void setValidLifeTime(Long validLifeTime){
+ this.validLifeTime = validLifeTime;
+ }
+
+ public Long getValidLifeTime(){
+ return validLifeTime;
+ }
+
+ public void setFamily(String family){
+ this.family = family;
+ }
+
+ public String getFamily(){
+ return family;
+ }
+
+ public void setPreferredLifeTime(Long preferredLifeTime){
+ this.preferredLifeTime = preferredLifeTime;
+ }
+
+ public Long getPreferredLifeTime(){
+ return preferredLifeTime;
+ }
+
+ public void setLocal(String local){
+ this.local = local;
+ }
+
+ public String getLocal(){
+ return local;
+ }
+
+ public void setLabel(String label){
+ this.label = label;
+ }
+
+ public String getLabel(){
+ return label;
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Address.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Address.java
new file mode 100644
index 0000000..c55e39f
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Address.java
@@ -0,0 +1,208 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Address{
+
+ @JsonProperty("qdisc")
+ private String qdisc;
+
+ @JsonProperty("ifindex")
+ private Integer ifindex;
+
+ @JsonProperty("max_mtu")
+ private Integer maxMtu;
+
+ @JsonProperty("flags")
+ private List<String> flags;
+
+ @JsonProperty("txqlen")
+ private Integer txqlen;
+
+ @JsonProperty("num_rx_queues")
+ private Integer numRxQueues;
+
+ @JsonProperty("min_mtu")
+ private Integer minMtu;
+
+ @JsonProperty("mtu")
+ private Integer mtu;
+
+ @JsonProperty("link_type")
+ private String linkType;
+
+ @JsonProperty("gso_max_segs")
+ private Integer gsoMaxSegs;
+
+ @JsonProperty("ifname")
+ private String ifname;
+
+ @JsonProperty("num_tx_queues")
+ private Integer numTxQueues;
+
+ @JsonProperty("promiscuity")
+ private Integer promiscuity;
+
+ @JsonProperty("operstate")
+ private String operstate;
+
+ @JsonProperty("addr_info")
+ private List<AddrInfoItem> addrInfo;
+
+ @JsonProperty("gso_max_size")
+ private Integer gsoMaxSize;
+
+ @JsonProperty("group")
+ private String group;
+
+ @JsonProperty("linkinfo")
+ private Linkinfo linkinfo;
+
+ public void setQdisc(String qdisc){
+ this.qdisc = qdisc;
+ }
+
+ public String getQdisc(){
+ return qdisc;
+ }
+
+ public void setIfindex(Integer ifindex){
+ this.ifindex = ifindex;
+ }
+
+ public Integer getIfindex(){
+ return ifindex;
+ }
+
+ public void setMaxMtu(Integer maxMtu){
+ this.maxMtu = maxMtu;
+ }
+
+ public Integer getMaxMtu(){
+ return maxMtu;
+ }
+
+ public void setFlags(List<String> flags){
+ this.flags = flags;
+ }
+
+ public List<String> getFlags(){
+ return flags;
+ }
+
+ public void setTxqlen(Integer txqlen){
+ this.txqlen = txqlen;
+ }
+
+ public Integer getTxqlen(){
+ return txqlen;
+ }
+
+ public void setNumRxQueues(Integer numRxQueues){
+ this.numRxQueues = numRxQueues;
+ }
+
+ public Integer getNumRxQueues(){
+ return numRxQueues;
+ }
+
+ public void setMinMtu(Integer minMtu){
+ this.minMtu = minMtu;
+ }
+
+ public Integer getMinMtu(){
+ return minMtu;
+ }
+
+ public void setMtu(Integer mtu){
+ this.mtu = mtu;
+ }
+
+ public Integer getMtu(){
+ return mtu;
+ }
+
+ public void setLinkType(String linkType){
+ this.linkType = linkType;
+ }
+
+ public String getLinkType(){
+ return linkType;
+ }
+
+ public void setGsoMaxSegs(Integer gsoMaxSegs){
+ this.gsoMaxSegs = gsoMaxSegs;
+ }
+
+ public Integer getGsoMaxSegs(){
+ return gsoMaxSegs;
+ }
+
+ public void setIfname(String ifname){
+ this.ifname = ifname;
+ }
+
+ public String getIfname(){
+ return ifname;
+ }
+
+ public void setNumTxQueues(Integer numTxQueues){
+ this.numTxQueues = numTxQueues;
+ }
+
+ public Integer getNumTxQueues(){
+ return numTxQueues;
+ }
+
+ public void setPromiscuity(Integer promiscuity){
+ this.promiscuity = promiscuity;
+ }
+
+ public Integer getPromiscuity(){
+ return promiscuity;
+ }
+
+ public void setOperstate(String operstate){
+ this.operstate = operstate;
+ }
+
+ public String getOperstate(){
+ return operstate;
+ }
+
+ public void setAddrInfo(List<AddrInfoItem> addrInfo){
+ this.addrInfo = addrInfo;
+ }
+
+ public List<AddrInfoItem> getAddrInfo(){
+ return addrInfo;
+ }
+
+ public void setGsoMaxSize(Integer gsoMaxSize){
+ this.gsoMaxSize = gsoMaxSize;
+ }
+
+ public Integer getGsoMaxSize(){
+ return gsoMaxSize;
+ }
+
+ public void setGroup(String group){
+ this.group = group;
+ }
+
+ public String getGroup(){
+ return group;
+ }
+
+ public void setLinkinfo(Linkinfo linkinfo){
+ this.linkinfo = linkinfo;
+ }
+
+ public Linkinfo getLinkinfo(){
+ return linkinfo;
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IP.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IP.java
new file mode 100644
index 0000000..109f206
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IP.java
@@ -0,0 +1,216 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import moe.yuuta.dn42peering.agent.IOUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class IP {
+ private static Logger logger = LoggerFactory.getLogger(IP.class.getSimpleName());
+
+ public static Future<String> batch(@Nonnull Vertx vertx,
+ @Nonnull IPOptions options,
+ @Nonnull List<String> commands) {
+ logger.info("Running batch ip commands:\n" +
+ String.join("\n", commands));
+ return vertx.executeBlocking(f -> {
+ final List<String> cmds = new ArrayList<>();
+ cmds.add("ip");
+ cmds.addAll(options.toCommand());
+ cmds.add("-b");
+ cmds.add("/dev/stdin");
+ logger.info("Executing " + cmds);
+ final ProcessBuilder builder = new ProcessBuilder()
+ .command(cmds.toArray(new String[]{}));
+ builder.environment().put("LANG", "C");
+
+ try {
+ final Process process = builder.start();
+ final OutputStream stdin = process.getOutputStream();
+ final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
+ writer.write(String.join("\n", commands));
+ writer.close();
+ final InputStream stdout = process.getInputStream();
+ final InputStream stderr = process.getErrorStream();
+ final int res = process.waitFor();
+ switch (res) {
+ case 0:
+ f.complete(IOUtils.read(stdout));
+ break;
+ case 1:
+ f.fail(new IPSyntaxException(IOUtils.read(stderr)));
+ break;
+ case 2:
+ f.fail(new IPKernelException(IOUtils.read(stderr)));
+ break;
+ default:
+ f.fail(new IPException(res, IOUtils.read(stderr)));
+ break;
+ }
+ } catch (IOException | InterruptedException e) {
+ f.fail(e);
+ }
+ });
+ }
+
+ @Nonnull
+ private static List<String> generateScript(@Nonnull String object,
+ @Nonnull String action,
+ @Nullable String... arguments) {
+ final List<String> cmds = new ArrayList<>();
+ cmds.add(object);
+ cmds.add(action);
+ if (arguments != null) cmds.addAll(Arrays.asList(arguments));
+ return cmds;
+ }
+
+ @Nonnull
+ public static Future<String> ip(@Nonnull Vertx vertx,
+ @Nonnull IPOptions options,
+ @Nonnull List<String> cmd) {
+ return vertx.executeBlocking(f -> {
+ final List<String> cmds = new ArrayList<>();
+ cmds.add("ip");
+ cmds.addAll(options.toCommand());
+ cmds.addAll(cmd);
+ logger.info("Executing '" + cmds + "'.");
+ final ProcessBuilder builder = new ProcessBuilder()
+ .command(cmds.toArray(new String[]{}));
+ builder.environment().put("LANG", "C");
+
+ try {
+ final Process process = builder.start();
+ final InputStream stdout = process.getInputStream();
+ final InputStream stderr = process.getErrorStream();
+ final int res = process.waitFor();
+ switch (res) {
+ case 0:
+ f.complete(IOUtils.read(stdout));
+ break;
+ case 1:
+ f.fail(new IPSyntaxException(IOUtils.read(stderr)));
+ break;
+ case 2:
+ f.fail(new IPKernelException(IOUtils.read(stderr)));
+ break;
+ default:
+ f.fail(new IPException(res, IOUtils.read(stderr)));
+ break;
+ }
+ } catch (IOException | InterruptedException e) {
+ f.fail(e);
+ }
+ });
+ }
+
+ @Nonnull
+ public static Future<String> ip(@Nonnull Vertx vertx,
+ @Nonnull IPOptions options,
+ @Nonnull String object,
+ @Nonnull String action,
+ @Nullable String... arguments) {
+ return ip(vertx, options, generateScript(object, action, arguments));
+ }
+
+ public static class Link {
+ private static final String OBJECT = "link";
+
+ public static Future<List<moe.yuuta.dn42peering.agent.ip.Link>> handler(@Nonnull String rawJson) {
+ final JsonArray arr = new JsonArray(rawJson);
+ return Future.succeededFuture(
+ arr.stream()
+ .map(obj -> ((JsonObject) obj).mapTo(moe.yuuta.dn42peering.agent.ip.Link.class))
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ public static List<String> show(@Nullable String device) {
+ return generateScript(OBJECT,
+ "show",
+ device);
+ }
+
+
+ @Nonnull
+ public static List<String> add(@Nonnull String device,
+ @Nonnull String type) {
+ return generateScript(OBJECT,
+ "add",
+ "dev", device,
+ "type", type);
+ }
+
+ @Nonnull
+ public static List<String> set(@Nonnull String device,
+ @Nonnull String... statements) {
+ final String[] arguments = new String[statements.length + 1];
+ arguments[0] = device;
+ System.arraycopy(statements, 0, arguments, 1, statements.length);
+ return generateScript(OBJECT,
+ "set",
+ arguments);
+ }
+
+ @Nonnull
+ public static List<String> del(@Nonnull String device) {
+ return generateScript(OBJECT,
+ "del",
+ "dev", device);
+ }
+ }
+
+ public static class Addr {
+ private static final String OBJECT = "addr";
+
+ @Nonnull
+ public static Future<List<Address>> handler(@Nonnull String rawJson) {
+ final JsonArray arr = new JsonArray(rawJson);
+ return Future.succeededFuture(
+ arr.stream()
+ .map(obj -> ((JsonObject) obj).mapTo(Address.class))
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ public static List<String> show(@Nullable String device) {
+ return generateScript(OBJECT,
+ "show",
+ device == null ? null : new String[] { device });
+ }
+
+ @Nonnull
+ public static List<String> add(@Nonnull String ifaddr,
+ @Nonnull String device,
+ @Nullable String peer) {
+ String[] args = new String[peer == null ? 3 : 5];
+ args[0] = ifaddr;
+ args[1] = "dev";
+ args[2] = device;
+ if (peer != null) {
+ args[3] = "peer";
+ args[4] = peer;
+ }
+ return generateScript(OBJECT,
+ "add",
+ args);
+ }
+
+ @Nonnull
+ public static List<String> flush(@Nullable String device) {
+ return generateScript(OBJECT,
+ "flush",
+ device);
+ }
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPException.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPException.java
new file mode 100644
index 0000000..0649a01
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPException.java
@@ -0,0 +1,12 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+public class IPException extends Exception {
+ public final int returnCode;
+ public final String stderr;
+
+ public IPException(int returnCode, String stderr) {
+ super(String.format("Failed to execute ip: %s", stderr));
+ this.returnCode = returnCode;
+ this.stderr = stderr;
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPKernelException.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPKernelException.java
new file mode 100644
index 0000000..ec2b94f
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPKernelException.java
@@ -0,0 +1,7 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+public class IPKernelException extends IPException {
+ public IPKernelException(String stderr) {
+ super(2, stderr);
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPOptions.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPOptions.java
new file mode 100644
index 0000000..45f3a5d
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPOptions.java
@@ -0,0 +1,82 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class IPOptions {
+ public enum Family {
+ INET,
+ INET6,
+ BRIDGE,
+ MPLS,
+ LINK
+ }
+
+ private boolean details;
+ private Family family;
+ private String netns;
+ private boolean force;
+
+ public boolean isDetails() {
+ return details;
+ }
+
+ @Nonnull
+ public IPOptions setDetails(boolean details) {
+ this.details = details;
+ return this;
+ }
+
+ public Family getFamily() {
+ return family;
+ }
+
+ @Nonnull
+ public IPOptions setFamily(Family family) {
+ this.family = family;
+ return this;
+ }
+
+ public String getNetns() {
+ return netns;
+ }
+
+ @Nonnull
+ public IPOptions setNetns(String netns) {
+ this.netns = netns;
+ return this;
+ }
+
+ public boolean isForce() {
+ return force;
+ }
+
+ @Nonnull
+ public IPOptions setForce(boolean force) {
+ this.force = force;
+ return this;
+ }
+
+ @Nonnull
+ List<String> toCommand() {
+ final List<String> cmds = new ArrayList<>();
+ if(details)
+ cmds.add("-details");
+ if(family != null) {
+ cmds.add("-family");
+ cmds.add(family.toString().toLowerCase(Locale.ROOT));
+ }
+ if(netns != null) {
+ cmds.add("-netns");
+ cmds.add(netns);
+ }
+ if(force) {
+ cmds.add("-force");
+ }
+ cmds.add("-json");
+ cmds.add("-c=never");
+ return cmds;
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPSyntaxException.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPSyntaxException.java
new file mode 100644
index 0000000..3d8d9db
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/IPSyntaxException.java
@@ -0,0 +1,7 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+public class IPSyntaxException extends IPException {
+ public IPSyntaxException(String stderr) {
+ super(1, stderr);
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Link.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Link.java
new file mode 100644
index 0000000..c27cd54
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Link.java
@@ -0,0 +1,219 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Link{
+
+ @JsonProperty("qdisc")
+ private String qdisc;
+
+ @JsonProperty("ifindex")
+ private Integer ifindex;
+
+ @JsonProperty("linkmode")
+ private String linkmode;
+
+ @JsonProperty("max_mtu")
+ private Integer maxMtu;
+
+ @JsonProperty("flags")
+ private List<String> flags;
+
+ @JsonProperty("txqlen")
+ private Integer txqlen;
+
+ @JsonProperty("num_rx_queues")
+ private Integer numRxQueues;
+
+ @JsonProperty("inet6_addr_gen_mode")
+ private String inet6AddrGenMode;
+
+ @JsonProperty("min_mtu")
+ private Integer minMtu;
+
+ @JsonProperty("mtu")
+ private Integer mtu;
+
+ @JsonProperty("link_type")
+ private String linkType;
+
+ @JsonProperty("gso_max_segs")
+ private Integer gsoMaxSegs;
+
+ @JsonProperty("ifname")
+ private String ifname;
+
+ @JsonProperty("num_tx_queues")
+ private Integer numTxQueues;
+
+ @JsonProperty("promiscuity")
+ private Integer promiscuity;
+
+ @JsonProperty("operstate")
+ private String operstate;
+
+ @JsonProperty("gso_max_size")
+ private Integer gsoMaxSize;
+
+ @JsonProperty("group")
+ private String group;
+
+ @JsonProperty("linkinfo")
+ private Linkinfo linkinfo;
+
+ public void setQdisc(String qdisc){
+ this.qdisc = qdisc;
+ }
+
+ public String getQdisc(){
+ return qdisc;
+ }
+
+ public void setIfindex(Integer ifindex){
+ this.ifindex = ifindex;
+ }
+
+ public Integer getIfindex(){
+ return ifindex;
+ }
+
+ public void setLinkmode(String linkmode){
+ this.linkmode = linkmode;
+ }
+
+ public String getLinkmode(){
+ return linkmode;
+ }
+
+ public void setMaxMtu(Integer maxMtu){
+ this.maxMtu = maxMtu;
+ }
+
+ public Integer getMaxMtu(){
+ return maxMtu;
+ }
+
+ public void setFlags(List<String> flags){
+ this.flags = flags;
+ }
+
+ public List<String> getFlags(){
+ return flags;
+ }
+
+ public void setTxqlen(Integer txqlen){
+ this.txqlen = txqlen;
+ }
+
+ public Integer getTxqlen(){
+ return txqlen;
+ }
+
+ public void setNumRxQueues(Integer numRxQueues){
+ this.numRxQueues = numRxQueues;
+ }
+
+ public Integer getNumRxQueues(){
+ return numRxQueues;
+ }
+
+ public void setInet6AddrGenMode(String inet6AddrGenMode){
+ this.inet6AddrGenMode = inet6AddrGenMode;
+ }
+
+ public String getInet6AddrGenMode(){
+ return inet6AddrGenMode;
+ }
+
+ public void setMinMtu(Integer minMtu){
+ this.minMtu = minMtu;
+ }
+
+ public Integer getMinMtu(){
+ return minMtu;
+ }
+
+ public void setMtu(Integer mtu){
+ this.mtu = mtu;
+ }
+
+ public Integer getMtu(){
+ return mtu;
+ }
+
+ public void setLinkType(String linkType){
+ this.linkType = linkType;
+ }
+
+ public String getLinkType(){
+ return linkType;
+ }
+
+ public void setGsoMaxSegs(Integer gsoMaxSegs){
+ this.gsoMaxSegs = gsoMaxSegs;
+ }
+
+ public Integer getGsoMaxSegs(){
+ return gsoMaxSegs;
+ }
+
+ public void setIfname(String ifname){
+ this.ifname = ifname;
+ }
+
+ public String getIfname(){
+ return ifname;
+ }
+
+ public void setNumTxQueues(Integer numTxQueues){
+ this.numTxQueues = numTxQueues;
+ }
+
+ public Integer getNumTxQueues(){
+ return numTxQueues;
+ }
+
+ public void setPromiscuity(Integer promiscuity){
+ this.promiscuity = promiscuity;
+ }
+
+ public Integer getPromiscuity(){
+ return promiscuity;
+ }
+
+ public void setOperstate(String operstate){
+ this.operstate = operstate;
+ }
+
+ public String getOperstate(){
+ return operstate;
+ }
+
+ public void setGsoMaxSize(Integer gsoMaxSize){
+ this.gsoMaxSize = gsoMaxSize;
+ }
+
+ public Integer getGsoMaxSize(){
+ return gsoMaxSize;
+ }
+
+ public void setGroup(String group){
+ this.group = group;
+ }
+
+ public String getGroup(){
+ return group;
+ }
+
+ public void setLinkinfo(Linkinfo linkinfo){
+ this.linkinfo = linkinfo;
+ }
+
+ public Linkinfo getLinkinfo(){
+ return linkinfo;
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Linkinfo.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Linkinfo.java
new file mode 100644
index 0000000..26b0875
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/ip/Linkinfo.java
@@ -0,0 +1,19 @@
+package moe.yuuta.dn42peering.agent.ip;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Linkinfo{
+
+ @JsonProperty("info_kind")
+ private String infoKind;
+
+ public void setInfoKind(String infoKind){
+ this.infoKind = infoKind;
+ }
+
+ public String getInfoKind(){
+ return infoKind;
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java
index 146e41c..5677ba1 100644
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java
@@ -31,7 +31,7 @@ public class FileChange extends Change {
public Future<Void> execute(@Nonnull Vertx vertx) {
switch (Action.valueOf(action)) {
case CREATE_AND_WRITE:
- logger.info("Writing " + id + " with:\n" + to);
+ logger.info("Writing " + id);
return vertx.fileSystem().open(id, new OpenOptions()
.setCreateNew(true)
.setTruncateExisting(true)
@@ -44,7 +44,7 @@ public class FileChange extends Change {
return asyncFile.close();
});
case OVERWRITE:
- logger.info("Overwriting " + id + " with:\n" + to);
+ logger.info("Overwriting " + id);
return vertx.fileSystem().open(id, new OpenOptions()
.setCreateNew(false)
.setTruncateExisting(true)
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IPChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IPChange.java
new file mode 100644
index 0000000..bc9c54d
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IPChange.java
@@ -0,0 +1,31 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import moe.yuuta.dn42peering.agent.ip.IP;
+import moe.yuuta.dn42peering.agent.ip.IPOptions;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public class IPChange extends Change {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private final boolean force;
+ private final List<String> commands;
+
+ public IPChange(boolean force, @Nonnull List<String> commands) {
+ super("ip", null, "exec");
+ this.force = force;
+ this.commands = commands;
+ }
+
+ @Nonnull
+ @Override
+ public Future<Void> execute(@Nonnull Vertx vertx) {
+ return IP.batch(vertx, new IPOptions().setForce(force), commands)
+ .compose(stdout -> Future.succeededFuture());
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java
index fbdbd3f..afdfae9 100644
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java
@@ -4,19 +4,22 @@ import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
-import io.vertx.core.file.FileSystemException;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.ext.web.common.template.TemplateEngine;
import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine;
+import moe.yuuta.dn42peering.agent.ip.AddrInfoItem;
+import moe.yuuta.dn42peering.agent.ip.Address;
+import moe.yuuta.dn42peering.agent.ip.IP;
+import moe.yuuta.dn42peering.agent.ip.IPOptions;
import moe.yuuta.dn42peering.agent.proto.Node;
import moe.yuuta.dn42peering.agent.proto.WireGuardConfig;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.Inet6Address;
-import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.stream.Collectors;
@@ -31,7 +34,7 @@ public class WireGuardProvisioner implements IProvisioner<WireGuardConfig> {
}
public WireGuardProvisioner(@Nonnull TemplateEngine engine,
- @Nonnull Vertx vertx) {
+ @Nonnull Vertx vertx) {
this.engine = engine;
this.vertx = vertx;
}
@@ -42,28 +45,10 @@ public class WireGuardProvisioner implements IProvisioner<WireGuardConfig> {
final List<String> actualNames = Arrays.stream(actualNamesRaw == null ? new String[]{} : actualNamesRaw)
.sorted()
.collect(Collectors.toList());
- final String[] desiredNames = allDesired
- .stream()
- .map(desired -> generateWGPath(desired.getInterface()))
- .sorted()
- .collect(Collectors.toList())
- .toArray(new String[]{});
- final List<Integer> toRemove = new ArrayList<>(actualNames.size());
- for (int i = 0; i < desiredNames.length; i ++) {
- toRemove.clear();
- for(int j = 0; j < actualNames.size(); j ++) {
- if(("/etc/wireguard/" + actualNames.get(j)).equals(desiredNames[i])) {
- toRemove.add(j);
- }
- }
- for (int j = 0; j < toRemove.size(); j ++) {
- actualNames.remove(toRemove.get(j).intValue());
- }
- }
return Future.succeededFuture(actualNames.stream()
.flatMap(string -> {
- return Arrays.stream(new Change[] {
- new CommandChange(new String[]{ "systemctl", "disable", "--now", "-q", "wg-quick@" + string }),
+ return Arrays.stream(new Change[]{
+ new CommandChange(new String[]{"systemctl", "disable", "--now", "-q", "wg-quick@" + string}),
new FileChange("/etc/wireguard/" + string, null, FileChange.Action.DELETE.toString())
});
})
@@ -71,129 +56,213 @@ public class WireGuardProvisioner implements IProvisioner<WireGuardConfig> {
}
@Nonnull
- private static String generateWGPath(@Nonnull String iif) {
- return String.format("/etc/wireguard/%s.conf", iif);
+ private Future<Buffer> renderConfig(@Nonnull WireGuardConfig config) {
+ final Map<String, Object> params = new HashMap<>(5);
+ params.put("listen_port", config.getListenPort());
+ params.put("self_priv_key", config.getSelfPrivKey());
+ params.put("preshared_key", config.getSelfPresharedSecret());
+ if (!config.getEndpoint().equals("")) {
+ params.put("endpoint", config.getEndpoint());
+ }
+ params.put("peer_pub_key", config.getPeerPubKey());
+
+ return engine.render(params, "wg.conf.ftlh");
+ }
+
+ @Nullable
+ private WireGuardConfig searchDesiredConfig(@Nonnull List<WireGuardConfig> configs,
+ @Nonnull String device) {
+ // TODO: Optimize algorithm
+ for (final WireGuardConfig config : configs) {
+ if(config.getInterface().equals(device))
+ return config;
+ }
+ return null;
+ }
+
+ @Nullable
+ private Address searchActualAddress(@Nonnull List<Address> addresses,
+ @Nonnull String device) {
+ // TODO: Optimize algorithm
+ for (final Address address : addresses) {
+ if(address.getIfname().equals(device))
+ return address;
+ }
+ return null;
}
@Nonnull
- private Future<Buffer> readConfig(@Nonnull String iif) {
- return Future.future(f -> {
- vertx.fileSystem()
- .readFile(generateWGPath(iif))
- .onFailure(err -> {
- if(err instanceof FileSystemException &&
- err.getCause() instanceof NoSuchFileException) {
- f.complete(null);
+ private List<String> calculateSingleNetlinkChanges(@Nonnull Node node,
+ @Nonnull WireGuardConfig desired,
+ @Nullable Address actual) throws IOException {
+ final boolean linkLocal =
+ !desired.getPeerIPv6().isEmpty() &&
+ Inet6Address.getByName(desired.getPeerIPv6()).isLinkLocalAddress();
+
+ final boolean desireIP6 = !desired.getPeerIPv6().isEmpty();
+ final boolean needCreateInterface = actual == null;
+ final boolean needCreateAddrs;
+ final boolean needUp;
+
+ if(actual == null) {
+ needCreateAddrs = true;
+ needUp = true;
+ } else {
+ needUp = !actual.getOperstate().equals("UP") &&
+ !actual.getOperstate().equals("UNKNOWN");
+ AddrInfoItem actualIP4 = null;
+ AddrInfoItem actualIP6 = null;
+ boolean excessiveIPs = false;
+ for (final AddrInfoItem item : actual.getAddrInfo()) {
+ switch (item.getFamily()) {
+ case "inet":
+ if(actualIP4 != null) {
+ excessiveIPs = true;
+ break;
} else {
- f.fail(err);
+ actualIP4 = item;
}
- })
- .onSuccess(f::complete);
- });
- }
+ break;
+ case "inet6":
+ if(actualIP6 != null) {
+ excessiveIPs = true;
+ break;
+ } else {
+ actualIP6 = item;
+ }
+ break;
+ default:
+ excessiveIPs = true;
+ break;
+ }
+ }
+ if(excessiveIPs || actualIP4 == null || (desireIP6 && actualIP6 == null) ||
+ (!desireIP6 && actualIP6 != null)) {
+ logger.info("Recreating addresses for " + desired.getId() + " since there are extra addresses or necessary addresses cannot be found.");
+ needCreateAddrs = true;
+ } else {
+ boolean needCreateIP4Addr =
+ actualIP4.getPrefixlen() != 32 ||
+ !node.getIpv4().equals(actualIP4.getLocal()) ||
+ !desired.getPeerIPv4().equals(actualIP4.getAddress());
+ boolean needCreateIP6Addr = false;
+ if(desireIP6) {
+ needCreateIP6Addr =
+ actualIP6.getPrefixlen() != (linkLocal ? 64 : 128) ||
+ !(linkLocal ? node.getIpv6() : node.getIpv6NonLL()).equals(actualIP6.getLocal()) ||
+ (linkLocal ? (actualIP6.getAddress() != null) :
+ !desired.getPeerIPv6().equals(actualIP6.getAddress()));
+ if(needCreateIP6Addr) {
+ logger.info("IPv6 addresses for " + desired.getId() + " is outdated.\n" +
+ "Prefixes match: " + (actualIP6.getPrefixlen() == (linkLocal ? 64 : 128)) + "\n" +
+ "Local addresses match: " + ((linkLocal ? node.getIpv6() : node.getIpv6NonLL()).equals(actualIP6.getLocal())) + "\n" +
+ "Peer addresses match: " + (linkLocal ? (actualIP6.getAddress() == null) :
+ desired.getPeerIPv6().equals(actualIP6.getAddress())));
+ }
+ }
+ needCreateAddrs = needCreateIP4Addr || needCreateIP6Addr;
+ if(needCreateAddrs)
+ logger.info("Recreating addresses for " + desired.getId() +
+ " since IPv4 or IPv6 information is updated: " + needCreateIP4Addr + ", " + needCreateIP6Addr + ".");
+ }
+ }
- @Nonnull
- private Future<Buffer> renderConfig(@Nonnull Node node, @Nonnull WireGuardConfig config) {
- final Map<String, Object> params = new HashMap<>(9);
- params.put("listen_port", config.getListenPort());
- params.put("self_priv_key", config.getSelfPrivKey());
- params.put("dev", config.getInterface());
- params.put("self_ipv4", node.getIpv4());
- params.put("peer_ipv4", config.getPeerIPv4());
- if (!config.getPeerIPv6().equals("")) {
- params.put("peer_ipv6", config.getPeerIPv6());
- try {
- final boolean ll = Inet6Address.getByName(config.getPeerIPv6()).isLinkLocalAddress();
- params.put("peer_ipv6_ll", ll);
- if(ll)
- params.put("self_ipv6", node.getIpv6());
+ final List<List<String>> changes = new ArrayList<>();
+ if(needCreateInterface)
+ changes.add(IP.Link.add(desired.getInterface(), "wireguard"));
+ if(needCreateAddrs) {
+ changes.add(IP.Addr.flush(desired.getInterface()));
+ changes.add(IP.Addr.add(node.getIpv4() + "/32",
+ desired.getInterface(),
+ desired.getPeerIPv4() + "/32"));
+ if(!desired.getPeerIPv6().isEmpty()) {
+ if(linkLocal)
+ changes.add(IP.Addr.add(node.getIpv6() + "/64",
+ desired.getInterface(),
+ null));
else
- params.put("self_ipv6", node.getIpv6NonLL());
- } catch (IOException e) {
- return Future.failedFuture(e);
+ changes.add(IP.Addr.add(node.getIpv6NonLL() + "/128",
+ desired.getInterface(),
+ desired.getPeerIPv6() + "/128"));
}
}
- params.put("preshared_key", config.getSelfPresharedSecret());
- if(!config.getEndpoint().equals("")) {
- params.put("endpoint", config.getEndpoint());
- }
- params.put("peer_pub_key", config.getPeerPubKey());
-
- return engine.render(params, "wg.conf.ftlh");
+ if(needUp)
+ changes.add(IP.Link.set(desired.getInterface(), "up"));
+ return changes
+ .stream().map(cmd -> String.join(" ", cmd))
+ .collect(Collectors.toList());
}
@Nonnull
- private Future<List<Change>> calculateSingleConfigChange(@Nonnull Node node,
- @Nonnull WireGuardConfig desiredConfig) {
- return CompositeFuture.all(readConfig(desiredConfig.getInterface()), renderConfig(node, desiredConfig))
- .compose(future -> {
- final Buffer actualBuff = future.resultAt(0);
- final String actual = actualBuff == null ? null : actualBuff.toString();
- final String desired = future.resultAt(1).toString();
- final List<Change> changes = new ArrayList<>(1);
- if(actual == null) {
- changes.add(new FileChange(generateWGPath(desiredConfig.getInterface()),
- desired,
- FileChange.Action.CREATE_AND_WRITE.toString()));
- changes.add(
- new CommandChange(new String[] { "systemctl",
- "enable",
- "--now",
- "-q",
- "wg-quick@" + desiredConfig.getInterface() }));
- } else if(!actual.equals(desired)) {
- // TODO: Smart reloading / restarting
- changes.add(new FileChange(generateWGPath(desiredConfig.getInterface()),
- desired,
- FileChange.Action.OVERWRITE.toString()));
- changes.add(
- new CommandChange(new String[] { "systemctl",
- "restart",
- "-q",
- "wg-quick@" + desiredConfig.getInterface() }));
+ private Future<List<Change>> calculateTotalNetlinkChanges(@Nonnull Node node,
+ @Nonnull List<WireGuardConfig> allDesired) {
+ return IP.ip(vertx, new IPOptions(), IP.Addr.show(null))
+ .compose(IP.Addr::handler)
+ .compose(addrs -> {
+ final List<String> ipCommands = new ArrayList<>();
+ for (final WireGuardConfig desired : allDesired) {
+ final Address actual = searchActualAddress(addrs, desired.getInterface());
+ try {
+ ipCommands.addAll(calculateSingleNetlinkChanges(node,
+ desired,
+ actual));
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ }
+ // Detect interfaces to delete
+ for (final Address address : addrs) {
+ if(!address.getLinkType().equals("none") ||
+ !address.getIfname().matches("wg_.*")) {
+ continue;
+ }
+ if(searchDesiredConfig(allDesired, address.getIfname()) == null)
+ ipCommands.add(String.join(" ", IP.Link.del(address.getIfname())));
+ }
+ final List<Change> changes = new ArrayList<>();
+ if(!ipCommands.isEmpty()) {
+ changes.add(new IPChange(true, ipCommands));
}
return Future.succeededFuture(changes);
});
}
@Nonnull
- private Future<List<Change>> calculateSingleServiceStatusChange(@Nonnull WireGuardConfig desiredConfig) {
- // Check if the service is not started or in wrong state.
- return AsyncShell.exec(vertx, "systemctl", "is-active", "wg-quick@" + desiredConfig.getInterface())
- .compose(res -> {
- if(res == 0) {
- return Future.succeededFuture(Collections.emptyList());
+ private Future<List<Change>> calculateTotalWireGuardChanges(@Nonnull Node node,
+ @Nonnull List<WireGuardConfig> allDesired) {
+ return CompositeFuture.join(allDesired.stream().map(desired -> {
+ return renderConfig(desired)
+ .compose(desiredConf -> {
+ return Future.succeededFuture(new WireGuardSyncConfChange(desired.getInterface(),
+ desiredConf.toString()));
+ });
+ }).collect(Collectors.toList()))
+ .compose(compositeFuture -> {
+ final List<Change> changes = new ArrayList<>(allDesired.size());
+ for (int i = 0; i < allDesired.size(); i ++) {
+ final Change change = compositeFuture.resultAt(i);
+ if(change == null) continue;
+ changes.add(change);
}
- return Future.succeededFuture(Collections.singletonList(
- new CommandChange(new String[] { "systemctl",
- "enable",
- "--now",
- "-q",
- "wg-quick@" + desiredConfig.getInterface() })
- ));
+ return Future.succeededFuture(changes);
});
}
@Nonnull
@Override
public Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<WireGuardConfig> allDesired) {
- final List<Future<List<Change>>> addOrModifyChanges =
- allDesired.stream().map(desired -> calculateSingleConfigChange(node, desired)).collect(Collectors.toList());
- final List<Future<List<Change>>> serviceChanges =
- allDesired.stream().map(this::calculateSingleServiceStatusChange).collect(Collectors.toList());
- final Future<List<Change>> deleteChanges =
- calculateDeleteChanges(allDesired);
- final List<Future> futures = new ArrayList<>(addOrModifyChanges.size() + 1);
- futures.addAll(addOrModifyChanges);
- futures.addAll(serviceChanges);
- futures.add(deleteChanges);
- return CompositeFuture.all(futures)
- .compose(compositeFuture -> {
- final List<Change> changes = new ArrayList<>(futures.size());
- for(int i = 0; i < futures.size(); i ++) {
- changes.addAll(compositeFuture.resultAt(i));
- }
- return Future.succeededFuture(changes);
- });
+ return calculateDeleteChanges(allDesired).compose(changes -> {
+ return calculateTotalNetlinkChanges(node, allDesired)
+ .compose(netlinkChanges -> {
+ changes.addAll(netlinkChanges);
+ return Future.succeededFuture(changes);
+ });
+ }).compose(changes -> {
+ return calculateTotalWireGuardChanges(node, allDesired)
+ .compose(wireguardChanges -> {
+ changes.addAll(wireguardChanges);
+ return Future.succeededFuture(changes);
+ });
+ });
}
}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardSyncConfChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardSyncConfChange.java
new file mode 100644
index 0000000..3c50694
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardSyncConfChange.java
@@ -0,0 +1,51 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+import moe.yuuta.dn42peering.agent.IOUtils;
+
+import javax.annotation.Nonnull;
+import java.io.*;
+
+public class WireGuardSyncConfChange extends Change {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private final String device;
+ private final String config;
+ public WireGuardSyncConfChange(@Nonnull String device,
+ @Nonnull String config) {
+ super(device, config, "syncconf");
+ this.device = device;
+ this.config = config;
+ }
+
+ @Nonnull
+ @Override
+ public Future<Void> execute(@Nonnull Vertx vertx) {
+ logger.info("Syncing config with " + device);
+ return vertx.executeBlocking(f -> {
+ try {
+ final Process process = new ProcessBuilder()
+ .command("wg", "syncconf", device, "/dev/stdin")
+ .start();
+ final OutputStream stdin = process.getOutputStream();
+ final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
+ writer.write(config);
+ writer.close();
+ final InputStream stderr = process.getErrorStream();
+ final int res = process.waitFor();
+ if(res != 0) {
+ f.fail("Unexpected 'wg syncconf " + device + " /dev/stdin' response: " + res + ".\n" +
+ "stderr = \n" +
+ IOUtils.read(stderr));
+ return;
+ }
+ f.complete();
+ } catch (IOException | InterruptedException e) {
+ f.fail(e);
+ }
+ });
+ }
+}
diff --git a/agent/src/main/resources/wg.conf.ftlh b/agent/src/main/resources/wg.conf.ftlh
index abd07cf..cc51ede 100644
--- a/agent/src/main/resources/wg.conf.ftlh
+++ b/agent/src/main/resources/wg.conf.ftlh
@@ -1,17 +1,6 @@
-# Automatically Generated by dn42peering Agent.
-
[Interface]
ListenPort = ${listen_port?long?c}
PrivateKey = ${self_priv_key}
-PostUp = ip addr add dev ${dev} ${self_ipv4}/32 peer ${peer_ipv4}/32
-<#if peer_ipv6??>
-<#if peer_ipv6_ll>
-PostUp = ip addr add dev ${dev} ${self_ipv6}/64
-<#else>
-PostUp = ip addr add dev ${dev} ${self_ipv6}/128 peer ${peer_ipv6}/128
-</#if>
-</#if>
-Table = off
[Peer]
PublicKey = ${peer_pub_key}
diff --git a/rpc-common/src/main/java/moe/yuuta/dn42peering/RPC.java b/rpc-common/src/main/java/moe/yuuta/dn42peering/RPC.java
index 3fe243b..18b3a0b 100644
--- a/rpc-common/src/main/java/moe/yuuta/dn42peering/RPC.java
+++ b/rpc-common/src/main/java/moe/yuuta/dn42peering/RPC.java
@@ -2,4 +2,9 @@ package moe.yuuta.dn42peering;
public final class RPC {
public static final int AGENT_PORT = 49200;
+
+ // Agents before v1.11
+ public static final int VERSION_LEGACY = -1;
+ // v1.11: Adds GetVersion() support.
+ public static final int VERSION_11 = 11;
}