aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-04-02 11:56:42 -0700
committerTrumeet <yuuta@yuuta.moe>2021-04-02 11:56:42 -0700
commitd1734e53e805ba44eb13c10a724ee3ca05013914 (patch)
treeb09dbe62ec6b5d9eefc378b5fab12c47585ada8d
parentafcc4bc0cd7846a175c43a430a3aece920689e9a (diff)
downloaddn42peering-d1734e53e805ba44eb13c10a724ee3ca05013914.tar
dn42peering-d1734e53e805ba44eb13c10a724ee3ca05013914.tar.gz
dn42peering-d1734e53e805ba44eb13c10a724ee3ca05013914.tar.bz2
dn42peering-d1734e53e805ba44eb13c10a724ee3ca05013914.zip
feat(central/agent/rpc): completely rewrite of provision handling
Now we are shifting from transactional operations (Central tells what to do like provision, reload or unprovision to nodes) to declarative configurations (Central renders a desired state of all BGP sessions and VPN tunnels and the agent will compare the desired state with actual state and merge changes). This greatly simplifies provision process and reduces atomic operations. It also simplifies locks as now, the only lock is deploy lock. However, the current implementation does not support result tracing. That is, all provision results are ignored and the provision status will not be updated nor the user will know whether a peer is successfully provisioned. This will be introduced later. Even if error tracking is more difficult, using this method or communication still results in great benifit in reducing errors. Nodes are now stateless: whenever a deploy is required, it compares all its local state to the desired state. Thus, issues will be likely solved by restarting. Lastly, unprovision operations will not block peer deletion. Their results will also be ignored. Breaking changes: * Not compatible with existing central / agents. They must be upgraded together. * Agents now must not be installed on the same machine.
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java7
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/grpc/AgentServiceImpl.java124
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java145
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java27
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java27
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java66
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java84
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java12
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java302
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java36
-rw-r--r--agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java199
-rw-r--r--agent/src/main/resources/bird2.conf.ftlh12
-rw-r--r--agent/src/main/resources/bird2_v4.conf.ftlh5
-rw-r--r--agent/src/main/resources/bird2_v6.conf.ftlh4
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java26
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java199
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/node/Node.java18
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java55
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java127
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java34
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java112
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java142
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java155
-rw-r--r--central/src/main/resources/manage/index.ftlh2
-rw-r--r--rpc-common/src/main/proto/agent.proto54
25 files changed, 662 insertions, 1312 deletions
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java
index e562ef7..fb9eead 100644
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/Main.java
@@ -5,11 +5,11 @@ import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
import moe.yuuta.dn42peering.agent.grpc.RPCVerticle;
-import moe.yuuta.dn42peering.agent.provision.ProvisionVerticle;
import javax.annotation.Nonnull;
import java.io.FileInputStream;
import java.io.InputStream;
+import java.util.Arrays;
public class Main {
public static void main(@Nonnull String... args) throws Throwable {
@@ -31,10 +31,9 @@ public class Main {
.setConfig(config)
.setInstances(Runtime.getRuntime().availableProcessors() * 2);
Logger logger = LoggerFactory.getLogger("Main");
- CompositeFuture.all(
- Future.<String>future(f -> vertx.deployVerticle(ProvisionVerticle.class.getName(), options, f)),
+ CompositeFuture.all(Arrays.asList(
Future.<String>future(f -> vertx.deployVerticle(RPCVerticle.class.getName(), options, f))
- ).onComplete(res -> {
+ )).onComplete(res -> {
if (res.succeeded()) {
logger.info("The server started.");
} else {
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 75b8184..c1ad43d 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
@@ -1,116 +1,50 @@
package moe.yuuta.dn42peering.agent.grpc;
+import io.vertx.core.CompositeFuture;
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.proto.*;
-import moe.yuuta.dn42peering.agent.provision.IProvisionService;
+import moe.yuuta.dn42peering.agent.proto.DeployResult;
+import moe.yuuta.dn42peering.agent.proto.NodeConfig;
+import moe.yuuta.dn42peering.agent.proto.VertxAgentGrpc;
+import moe.yuuta.dn42peering.agent.provision.BGPProvisioner;
+import moe.yuuta.dn42peering.agent.provision.Change;
+import moe.yuuta.dn42peering.agent.provision.WireGuardProvisioner;
import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
class AgentServiceImpl extends VertxAgentGrpc.AgentVertxImplBase {
private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
private final Vertx vertx;
- private final IProvisionService provisionService;
AgentServiceImpl(@Nonnull Vertx vertx) {
this.vertx = vertx;
- this.provisionService = IProvisionService.create(vertx);
}
@Override
- public Future<BGPReply> provisionBGP(BGPRequest request) {
- return Future.<Void>future(f -> provisionService.provisionBGP(
- request.getNode().getIpv4(),
- request.getNode().getIpv6(),
- request.getNode().getIpv6NonLL(),
- (int)request.getId(),
- request.getIpv4(),
- request.getIpv6().isEmpty() ? null : request.getIpv6(),
- request.getDevice(),
- request.getMpbgp(),
- request.getAsn(),
- f))
- .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build()))
- .onFailure(err -> logger.error(String.format("Cannot provision BGP for %d", request.getId()),
- err));
- }
-
- @Override
- public Future<BGPReply> reloadBGP(BGPRequest request) {
- return Future.<Void>future(f -> provisionService.reloadBGP(
- request.getNode().getIpv4(),
- request.getNode().getIpv6(),
- request.getNode().getIpv6NonLL(),
- (int)request.getId(),
- request.getIpv4(),
- request.getIpv6().isEmpty() ? null : request.getIpv6(),
- request.getDevice(),
- request.getMpbgp(),
- request.getAsn(),
- f))
- .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build()))
- .onFailure(err -> logger.error(String.format("Cannot reload BGP for %d", request.getId()),
- err));
- }
-
- @Override
- public Future<BGPReply> deleteBGP(BGPRequest request) {
- return Future.<Void>future(f -> provisionService.unprovisionBGP((int)request.getId(), f))
- .compose(_v -> Future.succeededFuture(BGPReply.newBuilder().build()))
- .onFailure(err -> logger.error(String.format("Cannot delete BGP for %d", request.getId()),
- err));
- }
-
- @Override
- public Future<WGReply> provisionWG(WGRequest request) {
- return Future.<String>future(f -> provisionService.provisionVPNWireGuard(
- request.getNode().getIpv4(),
- request.getNode().getIpv6(),
- request.getNode().getIpv6NonLL(),
- (int)request.getId(),
- request.getListenPort(),
- request.getEndpoint().isEmpty() ? null : request.getEndpoint(),
- request.getPeerPubKey(),
- request.getSelfPrivKey(),
- request.getSelfPresharedSecret(),
- request.getPeerIPv4(),
- request.getPeerIPv6().isEmpty() ? null : request.getPeerIPv6(),
- f))
- .compose(dev -> Future.succeededFuture(WGReply.newBuilder()
- .setDevice(dev).build()))
- .onFailure(err -> logger.error(String.format("Cannot provision WireGuard for %d", request.getId()),
- err));
- }
-
- @Override
- public Future<WGReply> reloadWG(WGRequest request) {
- return Future.<String>future(f -> provisionService.reloadVPNWireGuard(
- request.getNode().getIpv4(),
- request.getNode().getIpv6(),
- request.getNode().getIpv6NonLL(),
- (int)request.getId(),
- request.getListenPort(),
- request.getEndpoint().isEmpty() ? null : request.getEndpoint(),
- request.getPeerPubKey(),
- request.getSelfPrivKey(),
- request.getSelfPresharedSecret(),
- request.getPeerIPv4(),
- request.getPeerIPv6().isEmpty() ? null : request.getPeerIPv6(),
- f))
- .compose(dev -> Future.succeededFuture(WGReply.newBuilder()
- .setDevice(dev).build()))
- .onFailure(err -> logger.error(String.format("Cannot reload WireGuard for %d", request.getId()),
- err));
- }
-
- @Override
- public Future<WGReply> deleteWG(WGRequest request) {
- return Future.<Void>future(f -> provisionService.unprovisionVPNWireGuard((int)request.getId(), f))
- .compose(_v -> Future.succeededFuture(WGReply.newBuilder().build()))
- .onFailure(err -> logger.error(String.format("Cannot delete WireGuard for %d", request.getId()),
- err));
+ public Future<DeployResult> deploy(NodeConfig config) {
+ logger.info("Deployment started");
+ final BGPProvisioner bgpProvisioner = new BGPProvisioner(vertx);
+ final WireGuardProvisioner wireGuardProvisioner = new WireGuardProvisioner(vertx);
+ final List<Future> calcFutures = new ArrayList<>();
+ calcFutures.add(bgpProvisioner.calculateChanges(config.getNode(), config.getBgpsList()));
+ calcFutures.add(wireGuardProvisioner.calculateChanges(config.getNode(), config.getWgsList()));
+
+ return CompositeFuture.all(calcFutures)
+ .compose(compositeFuture -> {
+ final List<Future> changes = new ArrayList<>(calcFutures.size());
+ for (int i = 0; i < calcFutures.size(); i ++) {
+ final List<Change> list = compositeFuture.resultAt(i);
+ changes.addAll(list.stream().map(change -> change.execute(vertx)).collect(Collectors.toList()));
+ }
+ return CompositeFuture.all(changes);
+ })
+ .onComplete(res -> logger.info("Deployment finished. Detailed log can be traced above."))
+ .compose(compositeFuture -> Future.succeededFuture(null));
}
}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java
new file mode 100644
index 0000000..913f109
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/BGPProvisioner.java
@@ -0,0 +1,145 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+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.proto.BGPConfig;
+import moe.yuuta.dn42peering.agent.proto.Node;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.nio.file.NoSuchFileException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class BGPProvisioner implements IProvisioner<BGPConfig> {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private final TemplateEngine engine;
+ private final Vertx vertx;
+
+ public BGPProvisioner(@Nonnull Vertx vertx) {
+ this(FreeMarkerTemplateEngine.create(vertx, "ftlh"), vertx);
+ }
+
+ public BGPProvisioner(@Nonnull TemplateEngine engine,
+ @Nonnull Vertx vertx) {
+ this.engine = engine;
+ this.vertx = vertx;
+ }
+
+ @Nonnull
+ private Future<List<Change>> calculateDeleteChanges(@Nonnull List<BGPConfig> allDesired) {
+ final String[] actualNamesRaw = new File("/etc/bird/peers").list((dir, name) -> name.matches("dn42_.*\\.conf"));
+ final List<String> actualNames = Arrays.stream(actualNamesRaw == null ? new String[]{} : actualNamesRaw)
+ .sorted()
+ .collect(Collectors.toList());
+ final String[] desiredNames = allDesired
+ .stream()
+ .map(desired -> generateBGPPath(desired.getId()))
+ .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/bird/peers/" + 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()
+ .map(string -> new FileChange("/etc/bird/peers/" + string, null, FileChange.Action.DELETE.toString()))
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ private static String generateBGPPath(long id) {
+ return String.format("/etc/bird/peers/dn42_%d.conf", id);
+ }
+
+ @Nonnull
+ private Future<Buffer> readConfig(long id) {
+ return Future.future(f -> {
+ vertx.fileSystem()
+ .readFile(generateBGPPath(id))
+ .onFailure(err -> {
+ if(err instanceof FileSystemException &&
+ err.getCause() instanceof NoSuchFileException) {
+ f.complete(null);
+ } else {
+ f.fail(err);
+ }
+ })
+ .onSuccess(f::complete);
+ });
+ }
+
+ @Nonnull
+ private Future<Buffer> renderConfig(@Nonnull BGPConfig config) {
+ final Map<String, Object> params = new HashMap<>(3);
+ params.put("name", config.getId());
+ params.put("asn", config.getAsn());
+ params.put("ipv4", config.getIpv4());
+ params.put("ipv6", config.getIpv6().equals("") ? null : config.getIpv6());
+ params.put("mpbgp", config.getMpbgp());
+ params.put("dev", config.getInterface());
+ return engine.render(params, "bird2.conf.ftlh");
+ }
+
+ @Nonnull
+ private Future<List<Change>> calculateSingleChange(@Nonnull BGPConfig desiredConfig) {
+ return CompositeFuture.all(readConfig(desiredConfig.getId()), renderConfig(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(generateBGPPath(desiredConfig.getId()),
+ desired,
+ FileChange.Action.CREATE_AND_WRITE.toString()));
+ } else if(!actual.equals(desired)) {
+ changes.add(new FileChange(generateBGPPath(desiredConfig.getId()),
+ desired,
+ FileChange.Action.OVERWRITE.toString()));
+ }
+ return Future.succeededFuture(changes);
+ });
+ }
+
+ @Nonnull
+ @Override
+ public Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<BGPConfig> allDesired) {
+ final List<Future<List<Change>>> addOrModifyChanges =
+ allDesired.stream().map(this::calculateSingleChange).collect(Collectors.toList());
+ final Future<List<Change>> deleteChanges =
+ calculateDeleteChanges(allDesired);
+ final Future<List<Change>> reloadChange =
+ Future.succeededFuture(Collections.singletonList(
+ new CommandChange(new String[] { "birdc", "configure" })));
+
+ final List<Future> futures = new ArrayList<>(addOrModifyChanges.size() + 2);
+ futures.addAll(addOrModifyChanges);
+ futures.add(deleteChanges);
+ futures.add(reloadChange);
+ 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);
+ });
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java
new file mode 100644
index 0000000..b280e60
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/Change.java
@@ -0,0 +1,27 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public abstract class Change {
+ @Nonnull
+ public final String id;
+ @Nullable
+ public final String to;
+ @Nonnull
+ public final String action;
+
+ public Change(@Nonnull String id,
+ @Nullable String to,
+ @Nonnull String action) {
+ this.id = id;
+ this.to = to;
+ this.action = action;
+ }
+
+ @Nonnull
+ public abstract Future<Void> execute(@Nonnull Vertx vertx);
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java
new file mode 100644
index 0000000..ee60ee4
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/CommandChange.java
@@ -0,0 +1,27 @@
+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 javax.annotation.Nonnull;
+import java.util.Arrays;
+
+public class CommandChange extends Change {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+ @Nonnull
+ private final String[] commands;
+
+ public CommandChange(@Nonnull String[] commands) {
+ super(Arrays.toString(commands), null, "exec");
+ this.commands = commands;
+ }
+
+ @Nonnull
+ @Override
+ public Future<Void> execute(@Nonnull Vertx vertx) {
+ logger.info("Executing " + Arrays.toString(commands));
+ return AsyncShell.execSucc(vertx, commands);
+ }
+}
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
new file mode 100644
index 0000000..146e41c
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/FileChange.java
@@ -0,0 +1,66 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.file.OpenOptions;
+import io.vertx.core.impl.logging.Logger;
+import io.vertx.core.impl.logging.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class FileChange extends Change {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ public enum Action {
+ CREATE_AND_WRITE,
+ OVERWRITE,
+ DELETE
+ }
+
+ public FileChange(@Nonnull String path,
+ @Nullable String contents,
+ @Nonnull String action) {
+ super(path, contents, action);
+ Action.valueOf(action); // Verify
+ }
+
+ @Nonnull
+ @Override
+ public Future<Void> execute(@Nonnull Vertx vertx) {
+ switch (Action.valueOf(action)) {
+ case CREATE_AND_WRITE:
+ logger.info("Writing " + id + " with:\n" + to);
+ return vertx.fileSystem().open(id, new OpenOptions()
+ .setCreateNew(true)
+ .setTruncateExisting(true)
+ .setWrite(true))
+ .compose(asyncFile -> {
+ return asyncFile.write(Buffer.buffer(to == null ? "" : to), 0)
+ .compose(_v -> Future.succeededFuture(asyncFile));
+ })
+ .compose(asyncFile -> {
+ return asyncFile.close();
+ });
+ case OVERWRITE:
+ logger.info("Overwriting " + id + " with:\n" + to);
+ return vertx.fileSystem().open(id, new OpenOptions()
+ .setCreateNew(false)
+ .setTruncateExisting(true)
+ .setWrite(true))
+ .compose(asyncFile -> {
+ return asyncFile.write(Buffer.buffer(to == null ? "" : to), 0)
+ .compose(_v -> Future.succeededFuture(asyncFile));
+ })
+ .compose(asyncFile -> {
+ return asyncFile.close();
+ });
+ case DELETE:
+ logger.info("Deleting " + id);
+ return vertx.fileSystem().delete(id);
+ default:
+ throw new UnsupportedOperationException("Unknown file change action " + action);
+ }
+ }
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java
deleted file mode 100644
index c3d416c..0000000
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisionService.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package moe.yuuta.dn42peering.agent.provision;
-
-import io.vertx.codegen.annotations.Fluent;
-import io.vertx.codegen.annotations.ProxyGen;
-import io.vertx.core.AsyncResult;
-import io.vertx.core.Handler;
-import io.vertx.core.Vertx;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-@ProxyGen
-public interface IProvisionService {
- String ADDRESS = IProvisionService.class.getName();
-
- @Nonnull
- static IProvisionService create(@Nonnull Vertx vertx) {
- return new IProvisionServiceVertxEBProxy(vertx, ADDRESS);
- }
-
- @Fluent
- @Nonnull
- IProvisionService provisionBGP(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- @Nonnull String ipv4,
- @Nullable String ipv6,
- @Nullable String device,
- boolean mpbgp,
- @Nonnull String asn,
- @Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionService reloadBGP(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- @Nonnull String ipv4,
- @Nullable String ipv6,
- @Nullable String device,
- boolean mpbgp,
- @Nonnull String asn,
- @Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionService unprovisionBGP(int id, @Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionService provisionVPNWireGuard(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- int listenPort,
- @Nullable String endpointWithPort,
- @Nonnull String peerPubKey,
- @Nonnull String selfPrivKey,
- @Nonnull String selfPresharedSecret,
- @Nonnull String peerIPv4,
- @Nullable String peerIPv6,
- @Nonnull Handler<AsyncResult<String>> handler);
-
- @Fluent
- @Nonnull
- IProvisionService reloadVPNWireGuard(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- int listenPort,
- @Nullable String endpointWithPort,
- @Nonnull String peerPubKey,
- @Nonnull String selfPrivKey,
- @Nonnull String selfPresharedSecret,
- @Nonnull String peerIPv4,
- @Nullable String peerIPv6,
- @Nonnull Handler<AsyncResult<String>> handler);
-
- @Fluent
- @Nonnull
- IProvisionService unprovisionVPNWireGuard(int id, @Nonnull Handler<AsyncResult<Void>> handler);
-}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java
new file mode 100644
index 0000000..19c49a0
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/IProvisioner.java
@@ -0,0 +1,12 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+import io.vertx.core.Future;
+import moe.yuuta.dn42peering.agent.proto.Node;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public interface IProvisioner<T> {
+ @Nonnull
+ Future<List<Change>> calculateChanges(@Nonnull Node node, @Nonnull List<T> allDesired);
+}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java
deleted file mode 100644
index 733d111..0000000
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionServiceImpl.java
+++ /dev/null
@@ -1,302 +0,0 @@
-package moe.yuuta.dn42peering.agent.provision;
-
-import io.vertx.core.AsyncResult;
-import io.vertx.core.Future;
-import io.vertx.core.Handler;
-import io.vertx.core.Vertx;
-import io.vertx.core.file.AsyncFile;
-import io.vertx.core.file.OpenOptions;
-import io.vertx.core.impl.logging.Logger;
-import io.vertx.core.impl.logging.LoggerFactory;
-import io.vertx.ext.web.common.template.TemplateEngine;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.util.HashMap;
-import java.util.Map;
-
-class ProvisionServiceImpl implements IProvisionService {
- private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
-
- private final Vertx vertx;
- private final TemplateEngine engine;
-
- ProvisionServiceImpl(@Nonnull Vertx vertx, @Nonnull TemplateEngine engine) {
- this.vertx = vertx;
- this.engine = engine;
- }
-
- @Nonnull
- private static String generateBGPPath(int id) {
- return String.format("/etc/bird/peers/dn42_%d.conf", id);
- }
-
- @Nonnull
- private static String generateWGPath(@Nonnull String dev) {
- return String.format("/etc/wireguard/%s.conf", dev);
- }
-
- @Nonnull
- private static String generateWireGuardDevName(long id) {
- return String.format("wg_%d", id);
- }
-
- @Nonnull
- private static String getLockNameForBGP(long id) {
- return String.format("BGP:%d", id);
- }
-
- @Nonnull
- private static String getLockNameForWG(long id) {
- return String.format("WG:%d", id);
- }
-
- @Nonnull
- private Future<Void> writeBGPConfig(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- @Nonnull String ipv4,
- @Nullable String ipv6,
- @Nullable String device,
- boolean mpbgp,
- @Nonnull String asn,
- boolean create) {
- final String asnNum = asn.replace("AS", "");
- return vertx.fileSystem().open(generateBGPPath(id), new OpenOptions()
- .setCreateNew(create)
- .setTruncateExisting(true)
- .setWrite(true))
- .compose(asyncFile -> {
- if (mpbgp) return Future.succeededFuture(asyncFile);
- final Map<String, Object> params = new HashMap<>(3);
- params.put("name", id);
- params.put("ipv4", ipv4);
- params.put("asn", asnNum);
- return engine.render(params, "bird2_v4.conf.ftlh")
- .compose(buffer -> asyncFile.write(buffer)
- .compose(_v1 -> Future.succeededFuture(asyncFile)));
- })
- .compose(asyncFile -> {
- if (ipv6 == null) return Future.succeededFuture(asyncFile);
- final Map<String, Object> params = new HashMap<>(4);
- params.put("name", id);
- params.put("ipv6", ipv6);
- params.put("asn", asnNum);
- params.put("dev", device);
- return engine.render(params, "bird2_v6.conf.ftlh")
- .compose(buffer -> asyncFile.write(buffer)
- .compose(_v1 -> Future.succeededFuture(asyncFile)));
- })
- .compose(AsyncFile::close);
- }
-
- @Nonnull
- private Future<Void> writeWGConfig(boolean create,
- @Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- @Nonnull String dev,
- int listenPort,
- @Nullable String endpointWithPort,
- @Nonnull String peerPubKey,
- @Nonnull String selfPrivKey,
- @Nonnull String selfPresharedSecret,
- @Nonnull String peerIPv4,
- @Nullable String peerIPv6) {
- return vertx.fileSystem().open(generateWGPath(dev), new OpenOptions()
- .setCreateNew(create)
- .setTruncateExisting(true)
- .setWrite(true))
- .compose(asyncFile -> {
- final Map<String, Object> params = new HashMap<>(9);
- params.put("listen_port", listenPort);
- params.put("self_priv_key", selfPrivKey);
- params.put("dev", dev);
- params.put("self_ipv4", localIP4);
- params.put("peer_ipv4", peerIPv4);
- params.put("peer_ipv6", peerIPv6);
- if (peerIPv6 != null) {
- try {
- final boolean ll = Inet6Address.getByName(peerIPv6).isLinkLocalAddress();
- params.put("peer_ipv6_ll", ll);
- if(ll)
- params.put("self_ipv6", localIP6);
- else
- params.put("self_ipv6", localIP6NonLL);
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- }
- params.put("preshared_key", selfPresharedSecret);
- params.put("endpoint", endpointWithPort);
- params.put("peer_pub_key", peerPubKey);
-
- return engine.render(params, "wg.conf.ftlh")
- .compose(buffer -> asyncFile.write(buffer)
- .compose(_v1 -> Future.succeededFuture(asyncFile)));
- })
- .compose(AsyncFile::close);
- }
-
- @Nonnull
- private Future<Void> deleteBGPConfig(int id) {
- return vertx.fileSystem().exists(generateBGPPath(id))
- .compose(exists -> {
- if (exists) return vertx.fileSystem().delete(generateBGPPath(id));
- else return Future.succeededFuture(null);
- });
- }
-
- @Nonnull
- private Future<Void> deleteWGConfig(@Nonnull String dev) {
- return vertx.fileSystem().delete(generateWGPath(dev));
- }
-
- @Nonnull
- @Override
- public IProvisionService provisionBGP(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- @Nonnull String ipv4,
- @Nullable String ipv6,
- @Nullable String device,
- boolean mpbgp,
- @Nonnull String asn,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000)
- .compose(lock ->
- writeBGPConfig(localIP4, localIP6, localIP6NonLL, id, ipv4, ipv6, device, mpbgp, asn, true)
- .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure"))
- .onComplete(ar -> lock.release())
- )
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionService reloadBGP(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- @Nonnull String ipv4,
- @Nullable String ipv6,
- @Nullable String device,
- boolean mpbgp,
- @Nonnull String asn,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000)
- .compose(lock ->
- writeBGPConfig(localIP4, localIP6, localIP6NonLL, id, ipv4, ipv6, device, mpbgp, asn, false)
- .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure"))
- .onComplete(ar -> lock.release())
- )
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionService unprovisionBGP(int id, @Nonnull Handler<AsyncResult<Void>> handler) {
- vertx.sharedData().getLocalLockWithTimeout(getLockNameForBGP(id), 1000)
- .compose(lock ->
- deleteBGPConfig(id)
- .compose(_v -> AsyncShell.execSucc(vertx, "birdc", "configure"))
- .onComplete(ar -> lock.release())
- )
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionService provisionVPNWireGuard(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- int listenPort,
- @Nullable String endpointWithPort,
- @Nonnull String peerPubKey,
- @Nonnull String selfPrivKey,
- @Nonnull String selfPresharedSecret,
- @Nonnull String peerIPv4,
- @Nullable String peerIPv6,
- @Nonnull Handler<AsyncResult<String>> handler) {
- vertx.sharedData()
- .getLocalLockWithTimeout(getLockNameForWG(id), 1000)
- .compose(lock -> writeWGConfig(true,
- localIP4,
- localIP6,
- localIP6NonLL,
- generateWireGuardDevName(id),
- listenPort,
- endpointWithPort,
- peerPubKey,
- selfPrivKey,
- selfPresharedSecret,
- peerIPv4,
- peerIPv6)
- .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "enable", "--now", "-q",
- String.format("wg-quick@%s", generateWireGuardDevName(id))))
- .onComplete(_v -> lock.release()))
- .compose(_v -> Future.succeededFuture(generateWireGuardDevName(id)))
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionService reloadVPNWireGuard(@Nonnull String localIP4,
- @Nonnull String localIP6,
- @Nonnull String localIP6NonLL,
- int id,
- int listenPort,
- @Nullable String endpointWithPort,
- @Nonnull String peerPubKey,
- @Nonnull String selfPrivKey,
- @Nonnull String selfPresharedSecret,
- @Nonnull String peerIPv4,
- @Nullable String peerIPv6,
- @Nonnull Handler<AsyncResult<String>> handler) {
- vertx.sharedData()
- .getLocalLockWithTimeout(getLockNameForWG(id), 1000)
- .compose(lock -> writeWGConfig(false,
- localIP4,
- localIP6,
- localIP6NonLL,
- generateWireGuardDevName(id),
- listenPort,
- endpointWithPort,
- peerPubKey,
- selfPrivKey,
- selfPresharedSecret,
- peerIPv4,
- peerIPv6)
- .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "enable", "-q",
- String.format("wg-quick@%s", generateWireGuardDevName(id))))
- .compose(_v -> AsyncShell.execSucc(vertx, "systemctl", "reload-or-restart",
- String.format("wg-quick@%s", generateWireGuardDevName(id))))
- .onComplete(_v -> lock.release()))
- .compose(_v -> Future.succeededFuture(generateWireGuardDevName(id)))
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionService unprovisionVPNWireGuard(int id, @Nonnull Handler<AsyncResult<Void>> handler) {
- vertx.sharedData()
- .getLocalLockWithTimeout(getLockNameForWG(id), 1000)
- .compose(lock -> AsyncShell.execSucc(vertx, "systemctl", "disable", "--now", "-q",
- String.format("wg-quick@%s", generateWireGuardDevName(id)))
- // We need to stop the service first, then delete the configuration.
- .compose(_v -> deleteWGConfig(generateWireGuardDevName(id)))
- .onComplete(_v -> lock.release()))
- .onComplete(handler);
- return this;
- }
-}
diff --git a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java
deleted file mode 100644
index eb2c678..0000000
--- a/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/ProvisionVerticle.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package moe.yuuta.dn42peering.agent.provision;
-
-import io.vertx.core.AbstractVerticle;
-import io.vertx.core.Promise;
-import io.vertx.core.eventbus.MessageConsumer;
-import io.vertx.core.impl.logging.Logger;
-import io.vertx.core.impl.logging.LoggerFactory;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine;
-import io.vertx.serviceproxy.ServiceBinder;
-
-public class ProvisionVerticle extends AbstractVerticle {
- private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
-
- private MessageConsumer<JsonObject> consumer;
-
- @Override
- public void start(Promise<Void> startPromise) throws Exception {
- consumer = new ServiceBinder(vertx)
- .setAddress(IProvisionService.ADDRESS)
- .register(IProvisionService.class, new ProvisionServiceImpl(vertx,
- FreeMarkerTemplateEngine.create(vertx, "ftlh")));
- consumer.completionHandler(ar -> {
- if (ar.succeeded()) {
- startPromise.complete();
- } else {
- startPromise.fail(ar.cause());
- }
- });
- }
-
- @Override
- public void stop(Promise<Void> stopPromise) throws Exception {
- consumer.unregister(stopPromise);
- }
-}
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
new file mode 100644
index 0000000..de89953
--- /dev/null
+++ b/agent/src/main/java/moe/yuuta/dn42peering/agent/provision/WireGuardProvisioner.java
@@ -0,0 +1,199 @@
+package moe.yuuta.dn42peering.agent.provision;
+
+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.proto.Node;
+import moe.yuuta.dn42peering.agent.proto.WireGuardConfig;
+
+import javax.annotation.Nonnull;
+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;
+
+public class WireGuardProvisioner implements IProvisioner<WireGuardConfig> {
+ private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+ private final TemplateEngine engine;
+ private final Vertx vertx;
+
+ public WireGuardProvisioner(@Nonnull Vertx vertx) {
+ this(FreeMarkerTemplateEngine.create(vertx, "ftlh"), vertx);
+ }
+
+ public WireGuardProvisioner(@Nonnull TemplateEngine engine,
+ @Nonnull Vertx vertx) {
+ this.engine = engine;
+ this.vertx = vertx;
+ }
+
+ @Nonnull
+ private Future<List<Change>> calculateDeleteChanges(@Nonnull List<WireGuardConfig> allDesired) {
+ final String[] actualNamesRaw = new File("/etc/wireguard/").list((dir, name) -> name.matches("wg_.*\\.conf"));
+ 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 }),
+ new FileChange("/etc/wireguard/" + string, null, FileChange.Action.DELETE.toString())
+ });
+ })
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ private static String generateWGPath(@Nonnull String iif) {
+ return String.format("/etc/wireguard/%s.conf", iif);
+ }
+
+ @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);
+ } else {
+ f.fail(err);
+ }
+ })
+ .onSuccess(f::complete);
+ });
+ }
+
+ @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());
+ params.put("peer_ipv6", config.getPeerIPv6());
+ if (!config.getPeerIPv6().equals("")) {
+ try {
+ final boolean ll = Inet6Address.getByName(config.getPeerIPv6()).isLinkLocalAddress();
+ params.put("peer_ipv6_ll", ll);
+ if(ll)
+ params.put("self_ipv6", node.getIpv6());
+ else
+ params.put("self_ipv6", node.getIpv6NonLL());
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ }
+ 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");
+ }
+
+ @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() }));
+ }
+ 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());
+ }
+ return Future.succeededFuture(Collections.singletonList(
+ new CommandChange(new String[] { "systemctl",
+ "enable",
+ "--now",
+ "-q",
+ "wg-quick@" + desiredConfig.getInterface() })
+ ));
+ });
+ }
+
+ @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);
+ });
+ }
+}
diff --git a/agent/src/main/resources/bird2.conf.ftlh b/agent/src/main/resources/bird2.conf.ftlh
new file mode 100644
index 0000000..fa98469
--- /dev/null
+++ b/agent/src/main/resources/bird2.conf.ftlh
@@ -0,0 +1,12 @@
+<#if !mpbgp>
+protocol bgp dn42_${name?long?c} from dnpeers {
+ neighbor ${ipv4} as ${asn?long?c};
+ direct;
+}
+</#if>
+<#if ipv6??>
+protocol bgp dn42_${name?long?c}_v6 from dnpeers {
+ neighbor ${ipv6}%${dev} as ${asn?long?c};
+ direct;
+}
+</#if> \ No newline at end of file
diff --git a/agent/src/main/resources/bird2_v4.conf.ftlh b/agent/src/main/resources/bird2_v4.conf.ftlh
deleted file mode 100644
index ea10c9e..0000000
--- a/agent/src/main/resources/bird2_v4.conf.ftlh
+++ /dev/null
@@ -1,5 +0,0 @@
-protocol bgp dn42_${name} from dnpeers {
- neighbor ${ipv4} as ${asn};
- direct;
-}
-
diff --git a/agent/src/main/resources/bird2_v6.conf.ftlh b/agent/src/main/resources/bird2_v6.conf.ftlh
deleted file mode 100644
index 579fa84..0000000
--- a/agent/src/main/resources/bird2_v6.conf.ftlh
+++ /dev/null
@@ -1,4 +0,0 @@
-protocol bgp dn42_${name}_v6 from dnpeers {
- neighbor ${ipv6}%${dev} as ${asn};
- direct;
-} \ No newline at end of file
diff --git a/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java
index b6ff237..6b22858 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java
@@ -36,12 +36,12 @@ import moe.yuuta.dn42peering.whois.IWhoisService;
import javax.annotation.Nonnull;
import java.io.IOException;
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
import static io.vertx.ext.web.validation.builder.Parameters.param;
import static io.vertx.json.schema.common.dsl.Schemas.*;
import static moe.yuuta.dn42peering.manage.ManagementUI.*;
-import static moe.yuuta.dn42peering.manage.ManagementProvision.*;
public class ManageHandler implements ISubRouter {
private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
@@ -153,8 +153,7 @@ public class ManageHandler implements ISubRouter {
.setStatusCode(303)
.putHeader("Location", "/manage")
.end();
- provisionPeer(nodeService, provisionService, peer).onComplete(ar ->
- handleProvisionResult(peerService, peer, ar));
+ provisionService.deploy(peer.getNode(), ar -> {});
})
.onFailure(err -> {
if (err instanceof FormException) {
@@ -261,8 +260,8 @@ public class ManageHandler implements ISubRouter {
.end();
final Peer existingPeer = pair.a;
final Peer inPeer = pair.b;
- reloadPeer(nodeService, provisionService, existingPeer, inPeer).onComplete(ar ->
- handleProvisionResult(peerService, inPeer, ar));
+ provisionService.deploy(existingPeer.getNode(), ar -> {});
+ provisionService.deploy(inPeer.getNode(), ar -> {});
})
.onFailure(err -> {
if (err instanceof FormException) {
@@ -304,12 +303,15 @@ public class ManageHandler implements ISubRouter {
}
return Future.succeededFuture(peer);
})
- .compose(peer -> unprovisionPeer(nodeService, provisionService, peer))
- .compose(_v -> Future.<Void>future(f -> peerService.deletePeer(asn, id, f)))
- .onSuccess(_id -> ctx.response()
- .setStatusCode(303)
- .putHeader("Location", "/manage")
- .end())
+ .compose(peer -> Future.<Void>future(f -> peerService.deletePeer(asn, id, f))
+ .compose(_v1 -> Future.succeededFuture(peer)))
+ .onSuccess(peer -> {
+ ctx.response()
+ .setStatusCode(303)
+ .putHeader("Location", "/manage")
+ .end();
+ provisionService.deploy(peer.getNode(), ar -> {});
+ })
.onFailure(err -> {
if(!(err instanceof HTTPException)) logger.error("Cannot delete peer.", err);
ctx.fail(err);
diff --git a/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java
deleted file mode 100644
index 11ec50f..0000000
--- a/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package moe.yuuta.dn42peering.manage;
-
-import io.vertx.core.AsyncResult;
-import io.vertx.core.Future;
-import io.vertx.core.impl.logging.Logger;
-import io.vertx.core.impl.logging.LoggerFactory;
-import moe.yuuta.dn42peering.jaba.Pair;
-import moe.yuuta.dn42peering.node.INodeService;
-import moe.yuuta.dn42peering.node.Node;
-import moe.yuuta.dn42peering.peer.IPeerService;
-import moe.yuuta.dn42peering.peer.Peer;
-import moe.yuuta.dn42peering.peer.ProvisionStatus;
-import moe.yuuta.dn42peering.provision.BGPRequestCommon;
-import moe.yuuta.dn42peering.provision.IProvisionRemoteService;
-import moe.yuuta.dn42peering.provision.WGRequestCommon;
-
-import javax.annotation.Nonnull;
-import java.io.IOException;
-
-class ManagementProvision {
- private static final Logger logger = LoggerFactory.getLogger(ManagementProvision.class.getSimpleName());
-
- @Nonnull
- public static Future<Void> reloadPeer(@Nonnull INodeService nodeService,
- @Nonnull IProvisionRemoteService provisionService,
- @Nonnull Peer existingPeer, @Nonnull Peer inPeer) {
- // Check if we can reload on the fly.
- // Otherwise, we can only deprovision and provision.
- // This will cause unnecessary wastes.
- boolean canReload = inPeer.getType() == existingPeer.getType() &&
- inPeer.getNode() == existingPeer.getNode();
- // wg-quick does not support switching IP addresses.
- // TODO: Move reload detection to agents.
- if(canReload && // Only check if no other factors prevent us from reloading.
- inPeer.getType() == Peer.VPNType.WIREGUARD &&
- existingPeer.getType() == Peer.VPNType.WIREGUARD) {
- if(!inPeer.getIpv4().equals(existingPeer.getIpv6())) {
- canReload = false;
- }
- if(inPeer.getIpv6() != null && !inPeer.getIpv6().equals(existingPeer.getIpv6())) {
- try {
- // LL addrs does not have anything to do with ifconfig.
- if(inPeer.isIPv6LinkLocal() && existingPeer.isIPv6LinkLocal())
- canReload = true;
- else
- canReload = false;
- } catch (IOException ignored) {}
- }
- }
- // wg-quick will also not clear EndPoint setting if we just reload it.
- if(canReload && // Only check if no other factors prevent us from reloading.
- inPeer.getType() == Peer.VPNType.WIREGUARD &&
- existingPeer.getType() == Peer.VPNType.WIREGUARD) {
- if(inPeer.getWgEndpoint() == null &&
- existingPeer.getWgEndpoint() != null) {
- canReload = false;
- }
- }
- Future<Void> future;
- if (canReload) {
- future = Future.<Node>future(f -> nodeService.getNode(inPeer.getNode(), f))
- .compose(node -> {
- if(node == null || !node.getSupportedVPNTypes().contains(inPeer.getType())) {
- return Future.failedFuture("The node does not exist");
- }
- return Future.succeededFuture(node);
- })
- .compose(node -> {
- switch (existingPeer.getType()) {
- case WIREGUARD:
- final WGRequestCommon wgReq = inPeer.toWGRequest();
- wgReq.setNode(node.toRPCNode());
- return Future.<String>future(f -> provisionService.reloadWG(
- node.toRPCNode(),
- wgReq,
- f)
- ).compose(device -> Future.succeededFuture(new Pair<>(node, device)));
- default:
- throw new UnsupportedOperationException("Bug: Unknown type.");
- }
- })
- .compose(pair -> {
- final BGPRequestCommon bgpReq = inPeer.toBGPRequest();
- bgpReq.setNode(pair.a.toRPCNode());
- bgpReq.setDevice(pair.b);
- return Future.future(f -> provisionService.reloadBGP(
- pair.a.toRPCNode(),
- bgpReq,
- f));
- });
- } else {
- future = unprovisionPeer(nodeService, provisionService, existingPeer)
- .compose(f -> provisionPeer(nodeService, provisionService, inPeer));
- }
- return future;
- }
-
- public static Future<Void> unprovisionPeer(@Nonnull INodeService nodeService,
- @Nonnull IProvisionRemoteService provisionService,
- @Nonnull Peer existingPeer) {
- return Future.<Node>future(f -> nodeService.getNode(existingPeer.getNode(), f))
- .compose(node -> {
- if(node == null) {
- return Future.failedFuture("The node does not exist");
- }
- return Future.succeededFuture(node);
- })
- .compose(node -> {
- switch (existingPeer.getType()) {
- case WIREGUARD:
- return Future.<Void>future(f -> provisionService.deleteWG(
- node.toRPCNode(),
- new WGRequestCommon(null,
- (long)existingPeer.getId(),
- null,
- null,
- null,
- null,
- null,
- null,
- null),
- f))
- .compose(res -> Future.succeededFuture(node));
- default:
- throw new UnsupportedOperationException("Bug: Unknown type.");
- }
- })
- .compose(node -> {
- return Future.future(f -> provisionService.deleteBGP(
- node.toRPCNode(),
- new BGPRequestCommon(null,
- (long)existingPeer.getId(),
- null,
- null,
- null,
- null,
- null),
- f));
- })
- ;
- }
-
- @Nonnull
- public static Future<Void> provisionPeer(@Nonnull INodeService nodeService,
- @Nonnull IProvisionRemoteService provisionService,
- @Nonnull Peer inPeer) {
- return Future.<Node>future(f -> nodeService.getNode(inPeer.getNode(), f))
- .compose(node -> {
- if(node == null || !node.getSupportedVPNTypes().contains(inPeer.getType())) {
- return Future.failedFuture("The node does not exist");
- }
- return Future.succeededFuture(node);
- })
- .compose(node -> {
- switch (inPeer.getType()) {
- case WIREGUARD:
- final WGRequestCommon wgReq = inPeer.toWGRequest();
- wgReq.setNode(node.toRPCNode());
- return Future.<String>future(f -> provisionService.provisionWG(
- node.toRPCNode(),
- wgReq,
- f)
- ).compose(device -> Future.succeededFuture(new Pair<>(node, device)));
- default:
- throw new UnsupportedOperationException("Bug: Unknown type.");
- }
- })
- .compose(pair -> {
- final BGPRequestCommon bgpReq = inPeer.toBGPRequest();
- bgpReq.setNode(pair.a.toRPCNode());
- bgpReq.setDevice(pair.b);
- return Future.future(f -> provisionService.provisionBGP(
- pair.a.toRPCNode(),
- bgpReq,
- f));
- });
- }
-
- public static void handleProvisionResult(@Nonnull IPeerService peerService,
- @Nonnull Peer inPeer,
- @Nonnull AsyncResult<Void> res) {
- if(res.succeeded()) {
- peerService.changeProvisionStatus(inPeer.getId(),
- ProvisionStatus.PROVISIONED, ar -> {
- if (ar.failed()) {
- logger.error(String.format("Cannot update %d to provisioned.", inPeer.getId()), ar.cause());
- }
- });
- } else {
- logger.error(String.format("Cannot provision %d.", inPeer.getId()), res.cause());
- peerService.changeProvisionStatus(inPeer.getId(),
- ProvisionStatus.FAIL, ar -> {
- if (ar.failed()) {
- logger.error(String.format("Cannot update %d to failed.", inPeer.getId()), ar.cause());
- }
- });
- }
- }
-}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/node/Node.java b/central/src/main/java/moe/yuuta/dn42peering/node/Node.java
index 250930f..d4b5731 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/node/Node.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/node/Node.java
@@ -1,17 +1,13 @@
package moe.yuuta.dn42peering.node;
-import io.grpc.ManagedChannel;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.format.SnakeCase;
-import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
-import io.vertx.grpc.VertxChannelBuilder;
import io.vertx.sqlclient.templates.annotations.Column;
import io.vertx.sqlclient.templates.annotations.ParametersMapped;
import io.vertx.sqlclient.templates.annotations.RowMapped;
import io.vertx.sqlclient.templates.annotations.TemplateParameter;
-import moe.yuuta.dn42peering.provision.NodeCommon;
import moe.yuuta.dn42peering.peer.Peer;
import javax.annotation.Nonnull;
@@ -108,13 +104,13 @@ public class Node {
@GenIgnore
@Nonnull
- public NodeCommon toRPCNode() {
- return new NodeCommon(id,
- dn42Ip4,
- dn42Ip6,
- dn42Ip6NonLL,
- internalIp,
- internalPort);
+ public moe.yuuta.dn42peering.agent.proto.Node.Builder toRPCNode() {
+ return moe.yuuta.dn42peering.agent.proto.Node.newBuilder()
+ .setAsn(Long.parseLong(getAsn().substring(2)))
+ .setId(getId())
+ .setIpv4(getDn42Ip4())
+ .setIpv6(getDn42Ip6())
+ .setIpv6NonLL(getDn42Ip6NonLL());
}
// BEGIN GETTER / SETTER
diff --git a/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java b/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java
index b54f4e0..89082fd 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/peer/Peer.java
@@ -10,8 +10,8 @@ import io.vertx.sqlclient.templates.annotations.Column;
import io.vertx.sqlclient.templates.annotations.ParametersMapped;
import io.vertx.sqlclient.templates.annotations.RowMapped;
import io.vertx.sqlclient.templates.annotations.TemplateParameter;
-import moe.yuuta.dn42peering.provision.BGPRequestCommon;
-import moe.yuuta.dn42peering.provision.WGRequestCommon;
+import moe.yuuta.dn42peering.agent.proto.BGPConfig;
+import moe.yuuta.dn42peering.agent.proto.WireGuardConfig;
import javax.annotation.Nonnull;
import java.io.IOException;
@@ -191,31 +191,40 @@ public class Peer {
public boolean isIPv6LinkLocal() throws IOException {
return Inet6Address.getByName(ipv6).isLinkLocalAddress();
}
-
+
+ @Nonnull
@GenIgnore
- public WGRequestCommon toWGRequest() {
- return new WGRequestCommon(
- null,
- (long)id,
- Integer.parseInt(calcWireGuardPort()),
- wgEndpoint == null && wgEndpointPort == null ? "" :
- String.format("%s:%d", getWgEndpoint(), getWgEndpointPort()),
- wgPeerPubkey,
- wgSelfPrivKey,
- wgPresharedSecret,
- ipv4,
- ipv6 == null ? "" : ipv6);
+ public WireGuardConfig toWireGuardConfig() {
+ if(type != VPNType.WIREGUARD) throw new IllegalStateException("The VPN type is not WireGuard.");
+ return WireGuardConfig.newBuilder()
+ .setId(id)
+ .setListenPort(Integer.parseInt(calcWireGuardPort()))
+ .setEndpoint(wgEndpoint == null && wgEndpointPort == null ? "" :
+ String.format("%s:%d", getWgEndpoint(), getWgEndpointPort()))
+ .setPeerPubKey(wgPeerPubkey)
+ .setSelfPrivKey(wgSelfPrivKey)
+ .setSelfPresharedSecret(wgPresharedSecret)
+ .setPeerIPv4(ipv4)
+ .setPeerIPv6(ipv6 == null ? "" : ipv6)
+ .setInterface(calculateWireGuardInterfaceName())
+ .build();
}
@GenIgnore
- public BGPRequestCommon toBGPRequest() {
- return new BGPRequestCommon(null,
- (long)id,
- asn,
- mpbgp,
- ipv4,
- ipv6 == null ? "" : ipv6,
- null);
+ public BGPConfig toBGPConfig() {
+ return BGPConfig.newBuilder()
+ .setId(id)
+ .setAsn(Long.parseLong(asn.substring(2)))
+ .setMpbgp(mpbgp)
+ .setIpv4(ipv4)
+ .setIpv6(ipv6 == null ? "" : ipv6)
+ .setInterface(calculateWireGuardInterfaceName())
+ .build();
+ }
+
+ @Nonnull
+ public String calculateWireGuardInterfaceName() {
+ return String.format("wg_%d", id);
}
// START GETTERS / SETTERS
diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java
deleted file mode 100644
index 8ca9ea2..0000000
--- a/central/src/main/java/moe/yuuta/dn42peering/provision/BGPRequestCommon.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package moe.yuuta.dn42peering.provision;
-
-import io.vertx.codegen.annotations.DataObject;
-import io.vertx.core.json.JsonObject;
-import moe.yuuta.dn42peering.agent.proto.BGPRequest;
-
-import javax.annotation.Nonnull;
-
-@DataObject
-public class BGPRequestCommon {
- private NodeCommon node;
- private Long id;
- private String asn;
- private Boolean mpbgp;
- private String ipv4;
- private String ipv6;
- private String device;
-
- public BGPRequestCommon(NodeCommon node,
- Long id,
- String asn,
- Boolean mpbgp,
- String ipv4,
- String ipv6,
- String device) {
- this.node = node;
- this.id = id;
- this.asn = asn;
- this.mpbgp = mpbgp;
- this.ipv4 = ipv4;
- this.ipv6 = ipv6;
- this.device = device;
- }
-
- public BGPRequestCommon(@Nonnull JsonObject json) {
- if(json.getValue("node") != null) this.node = new NodeCommon(json.getJsonObject("node"));
- if(json.getValue("id") != null) this.id = json.getLong("id");
- this.asn = json.getString("asn");
- if(json.getValue("mpbgp") != null) this.mpbgp = json.getBoolean("mpbgp");
- this.ipv4 = json.getString("ipv4");
- this.ipv6 = json.getString("ipv6");
- this.device = json.getString("device");
- }
-
- @Nonnull
- public JsonObject toJson() {
- return new JsonObject()
- .put("node", node == null ? null : node.toJson())
- .put("id", id)
- .put("asn", asn)
- .put("mpbgp", mpbgp)
- .put("ipv4", ipv4)
- .put("ipv6", ipv6)
- .put("device", device);
- }
-
- @Nonnull
- public BGPRequest toGRPC() {
- final BGPRequest.Builder builder = BGPRequest.newBuilder();
- if(node != null) builder.setNode(node.toGRPC());
- if(id != null) builder.setId(id);
- if(asn != null) builder.setAsn(asn);
- if(mpbgp != null) builder.setMpbgp(mpbgp);
- if(ipv4 != null) builder.setIpv4(ipv4);
- if(ipv6 != null) builder.setIpv6(ipv6);
- if(device != null) builder.setDevice(device);
- return builder.build();
- }
-
- // Getters & Setters
-
- public NodeCommon getNode() {
- return node;
- }
-
- public void setNode(NodeCommon node) {
- this.node = node;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getAsn() {
- return asn;
- }
-
- public void setAsn(String asn) {
- this.asn = asn;
- }
-
- public Boolean getMpbgp() {
- return mpbgp;
- }
-
- public void setMpbgp(Boolean mpbgp) {
- this.mpbgp = mpbgp;
- }
-
- public String getIpv4() {
- return ipv4;
- }
-
- public void setIpv4(String ipv4) {
- this.ipv4 = ipv4;
- }
-
- public String getIpv6() {
- return ipv6;
- }
-
- public void setIpv6(String ipv6) {
- this.ipv6 = ipv6;
- }
-
- public String getDevice() {
- return device;
- }
-
- public void setDevice(String device) {
- this.device = device;
- }
-}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java b/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java
index ace1c54..16433ca 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/provision/IProvisionRemoteService.java
@@ -5,7 +5,6 @@ import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
-import moe.yuuta.dn42peering.node.Node;
import javax.annotation.Nonnull;
@@ -20,37 +19,6 @@ public interface IProvisionRemoteService {
@Fluent
@Nonnull
- IProvisionRemoteService provisionBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
+ IProvisionRemoteService deploy(long nodeId,
@Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionRemoteService reloadBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionRemoteService deleteBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler);
-
- @Fluent
- @Nonnull
- IProvisionRemoteService provisionWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<String>> handler);
-
- @Fluent
- @Nonnull
- IProvisionRemoteService reloadWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<String>> handler);
-
- @Fluent
- @Nonnull
- IProvisionRemoteService deleteWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler);
}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java
deleted file mode 100644
index 316965c..0000000
--- a/central/src/main/java/moe/yuuta/dn42peering/provision/NodeCommon.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package moe.yuuta.dn42peering.provision;
-
-import io.vertx.codegen.annotations.DataObject;
-import io.vertx.core.json.JsonObject;
-import moe.yuuta.dn42peering.agent.proto.Node;
-
-import javax.annotation.Nonnull;
-
-@DataObject
-public class NodeCommon {
- private Long id;
- private String ipv4;
- private String ipv6;
- private String ipv6NonLL;
- private String internalIp;
- private int internalPort;
-
- public NodeCommon(long id,
- @Nonnull String ipv4,
- @Nonnull String ipv6,
- @Nonnull String ipv6NonLL,
- @Nonnull String internalIp,
- @Nonnull Integer internalPort) {
- this.id = id;
- this.ipv4 = ipv4;
- this.ipv6 = ipv6;
- this.ipv6NonLL = ipv6NonLL;
- this.internalIp = internalIp;
- this.internalPort = internalPort;
- }
-
- public NodeCommon(@Nonnull JsonObject json) {
- if(json.getValue("id") != null) this.id = json.getLong("id");
- else this.id = null;
- this.ipv4 = json.getString("ipv4");
- this.ipv6 = json.getString("ipv6");
- this.ipv6NonLL = json.getString("ipv6_non_ll");
- this.internalIp = json.getString("internal_ip");
- if(json.getValue("internal_port") != null) this.internalPort = json.getInteger("internal_port");
- }
-
- @Nonnull
- public JsonObject toJson() {
- return new JsonObject()
- .put("id", id)
- .put("ipv4", ipv4)
- .put("ipv6", ipv6)
- .put("ipv6_non_ll", ipv6NonLL)
- .put("internal_ip", internalIp)
- .put("internal_port", internalPort);
- }
-
- @Nonnull
- public Node toGRPC() {
- final Node.Builder builder = Node.newBuilder()
- .setIpv4(ipv4)
- .setIpv6(ipv6)
- .setIpv6NonLL(ipv6NonLL);
- if(id != null) builder.setId(id);
- return builder.build();
- }
-
- // Getters & Getters
-
- public Long getId() {
- return id;
- }
-
- public String getIpv4() {
- return ipv4;
- }
-
- public String getIpv6() {
- return ipv6;
- }
-
- public String getIpv6NonLL() {
- return ipv6NonLL;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public void setIpv4(String ipv4) {
- this.ipv4 = ipv4;
- }
-
- public void setIpv6(String ipv6) {
- this.ipv6 = ipv6;
- }
-
- public void setIpv6NonLL(String ipv6NonLL) {
- this.ipv6NonLL = ipv6NonLL;
- }
-
- String getInternalIp() {
- return internalIp;
- }
-
- void setInternalIp(String internalIp) {
- this.internalIp = internalIp;
- }
-
- int getInternalPort() {
- return internalPort;
- }
-
- void setInternalPort(int internalPort) {
- this.internalPort = internalPort;
- }
-}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java b/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java
index a642326..3e432bd 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/provision/ProvisionRemoteServiceImpl.java
@@ -1,106 +1,88 @@
package moe.yuuta.dn42peering.provision;
import io.grpc.ManagedChannel;
-import io.vertx.core.*;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
import io.vertx.grpc.VertxChannelBuilder;
+import moe.yuuta.dn42peering.agent.proto.NodeConfig;
import moe.yuuta.dn42peering.agent.proto.VertxAgentGrpc;
+import moe.yuuta.dn42peering.jaba.Pair;
+import moe.yuuta.dn42peering.node.INodeService;
import moe.yuuta.dn42peering.node.Node;
+import moe.yuuta.dn42peering.peer.IPeerService;
+import moe.yuuta.dn42peering.peer.Peer;
import javax.annotation.Nonnull;
+import java.util.List;
class ProvisionRemoteServiceImpl implements IProvisionRemoteService {
private final Vertx vertx;
+ private final INodeService nodeService;
+ private final IPeerService peerService;
ProvisionRemoteServiceImpl(@Nonnull Vertx vertx) {
this.vertx = vertx;
+ this.nodeService = INodeService.createProxy(vertx);
+ this.peerService = IPeerService.createProxy(vertx);
}
- private @Nonnull ManagedChannel toChannel(@Nonnull NodeCommon node) {
+ private @Nonnull ManagedChannel toChannel(@Nonnull Node node) {
return VertxChannelBuilder.forAddress(vertx, node.getInternalIp(), node.getInternalPort())
.usePlaintext()
.build();
}
-
- @Nonnull
- @Override
- public IProvisionRemoteService provisionBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.provisionBGP(request.toGRPC())
- .<Void>compose(reply -> Future.succeededFuture(null))
- .onComplete(res -> channel.shutdown())
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionRemoteService reloadBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.reloadBGP(request.toGRPC())
- .<Void>compose(reply -> Future.succeededFuture(null))
- .onComplete(res -> channel.shutdown())
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionRemoteService deleteBGP(@Nonnull NodeCommon node,
- @Nonnull BGPRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.deleteBGP(request.toGRPC())
- .<Void>compose(reply -> Future.succeededFuture(null))
- .onComplete(res -> channel.shutdown())
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionRemoteService provisionWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<String>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.provisionWG(request.toGRPC())
- .compose(wgReply -> Future.succeededFuture(wgReply.getDevice()))
- .onComplete(res -> channel.shutdown())
- .onComplete(handler);
- return this;
- }
-
- @Nonnull
- @Override
- public IProvisionRemoteService reloadWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<String>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.reloadWG(request.toGRPC())
- .compose(wgReply -> Future.succeededFuture(wgReply.getDevice()))
- .onComplete(res -> channel.shutdown())
- .onComplete(handler);
- return this;
- }
@Nonnull
@Override
- public IProvisionRemoteService deleteWG(@Nonnull NodeCommon node,
- @Nonnull WGRequestCommon request,
- @Nonnull Handler<AsyncResult<Void>> handler) {
- final ManagedChannel channel = toChannel(node);
- final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
- stub.deleteWG(request.toGRPC())
- .<Void>compose(wgReply -> Future.succeededFuture(null))
- .onComplete(res -> channel.shutdown())
+ public IProvisionRemoteService deploy(long nodeId,
+ @Nonnull Handler<AsyncResult<Void>> handler) {
+ vertx.sharedData().getLockWithTimeout("deploy_" + nodeId, 30 * 1000)
+ .<Void>compose(lock -> {
+ return Future.<moe.yuuta.dn42peering.node.Node>future(f -> nodeService.getNode((int)nodeId, f))
+ .compose(node -> {
+ if (node == null) {
+ return Future.failedFuture("Invalid node");
+ } else {
+ return Future.succeededFuture(node);
+ }
+ })
+ .compose(node -> {
+ final NodeConfig.Builder builder = NodeConfig.newBuilder();
+ builder.setNode(node.toRPCNode().build());
+ return Future.succeededFuture(new Pair<>(node, builder));
+ })
+ .compose(pair -> {
+ final Node node = pair.a;
+ final NodeConfig.Builder builder = pair.b;
+ return Future.<List<Peer>>future(f -> peerService.listUnderNode(node.getId(), f))
+ .compose(peers -> {
+ peers.forEach(peer -> {
+ builder.addBgps(peer.toBGPConfig());
+ switch (peer.getType()) {
+ case WIREGUARD:
+ builder.addWgs(peer.toWireGuardConfig());
+ break;
+ default:
+ throw new IllegalArgumentException("Bug: Unsupported VPN type");
+ }
+ });
+ return Future.succeededFuture(pair);
+ });
+ })
+ .compose(pair -> {
+ final ManagedChannel channel = toChannel(pair.a);
+ final VertxAgentGrpc.AgentVertxStub stub = VertxAgentGrpc.newVertxStub(channel);
+ return stub.deploy(pair.b.build())
+ .<Void>compose(reply -> Future.succeededFuture(null))
+ .onComplete(res -> channel.shutdown());
+ })
+ .compose(_v -> {
+ lock.release();
+ return Future.succeededFuture();
+ });
+ })
.onComplete(handler);
return this;
}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java b/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java
deleted file mode 100644
index d4694b6..0000000
--- a/central/src/main/java/moe/yuuta/dn42peering/provision/WGRequestCommon.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package moe.yuuta.dn42peering.provision;
-
-import io.vertx.codegen.annotations.DataObject;
-import io.vertx.core.json.JsonObject;
-import moe.yuuta.dn42peering.agent.proto.WGRequest;
-
-import javax.annotation.Nonnull;
-
-@DataObject
-public class WGRequestCommon {
- private NodeCommon node;
- private Long id;
- private Integer listenPort;
- private String endpoint;
- private String peerPubKey;
- private String selfPrivKey;
- private String selfPresharedSecret;
- private String peerIPv4;
- private String peerIPv6;
-
- public WGRequestCommon(NodeCommon node,
- Long id,
- Integer listenPort,
- String endpoint,
- String peerPubKey,
- String selfPrivKey,
- String selfPresharedSecret,
- String peerIPv4,
- String peerIPv6) {
- this.node = node;
- this.id = id;
- this.listenPort = listenPort;
- this.endpoint = endpoint;
- this.peerPubKey = peerPubKey;
- this.selfPrivKey = selfPrivKey;
- this.selfPresharedSecret = selfPresharedSecret;
- this.peerIPv4 = peerIPv4;
- this.peerIPv6 = peerIPv6;
- }
-
- public WGRequestCommon(@Nonnull JsonObject json) {
- if(json.getValue("node") != null) this.node = new NodeCommon(json.getJsonObject("node"));
- if(json.getValue("id") != null) this.id = json.getLong("id");
- if(json.getValue("listen_port") != null) this.listenPort = json.getInteger("listen_port");
- this.endpoint = json.getString("endpoint");
- this.peerPubKey = json.getString("peer_public_key");
- this.selfPrivKey = json.getString("self_private_key");
- this.selfPresharedSecret = json.getString("self_preshared_secret");
- this.peerIPv4 = json.getString("peer_ipv4");
- this.peerIPv6 = json.getString("peer_ipv6");
- }
-
- @Nonnull
- public JsonObject toJson() {
- return new JsonObject()
- .put("node", node == null ? null : node.toJson())
- .put("id", id)
- .put("listen_port", listenPort)
- .put("endpoint", endpoint)
- .put("peer_public_key", peerPubKey)
- .put("self_private_key", selfPrivKey)
- .put("self_preshared_secret", selfPresharedSecret)
- .put("peer_ipv4", peerIPv4)
- .put("peer_ipv6", peerIPv6);
- }
-
- @Nonnull
- public WGRequest toGRPC() {
- final WGRequest.Builder builder = WGRequest.newBuilder();
- if(node != null) builder.setNode(node.toGRPC());
- if(id != null) builder.setId(id);
- if(listenPort != null) builder.setListenPort(listenPort);
- if(endpoint != null) builder.setEndpoint(endpoint);
- if(peerPubKey != null) builder.setPeerPubKey(peerPubKey);
- if(selfPrivKey != null) builder.setSelfPrivKey(selfPrivKey);
- if(selfPresharedSecret != null) builder.setSelfPresharedSecret(selfPresharedSecret);
- if(peerIPv4 != null) builder.setPeerIPv4(peerIPv4);
- if(peerIPv6 != null) builder.setPeerIPv6(peerIPv6);
- return builder.build();
- }
-
- // Getters & Setters
-
- public NodeCommon getNode() {
- return node;
- }
-
- public void setNode(NodeCommon node) {
- this.node = node;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public Integer getListenPort() {
- return listenPort;
- }
-
- public void setListenPort(Integer listenPort) {
- this.listenPort = listenPort;
- }
-
- public String getEndpoint() {
- return endpoint;
- }
-
- public void setEndpoint(String endpoint) {
- this.endpoint = endpoint;
- }
-
- public String getPeerPubKey() {
- return peerPubKey;
- }
-
- public void setPeerPubKey(String peerPubKey) {
- this.peerPubKey = peerPubKey;
- }
-
- public String getSelfPrivKey() {
- return selfPrivKey;
- }
-
- public void setSelfPrivKey(String selfPrivKey) {
- this.selfPrivKey = selfPrivKey;
- }
-
- public String getSelfPresharedSecret() {
- return selfPresharedSecret;
- }
-
- public void setSelfPresharedSecret(String selfPresharedSecret) {
- this.selfPresharedSecret = selfPresharedSecret;
- }
-
- public String getPeerIPv4() {
- return peerIPv4;
- }
-
- public void setPeerIPv4(String peerIPv4) {
- this.peerIPv4 = peerIPv4;
- }
-
- public String getPeerIPv6() {
- return peerIPv6;
- }
-
- public void setPeerIPv6(String peerIPv6) {
- this.peerIPv6 = peerIPv6;
- }
-}
diff --git a/central/src/main/resources/manage/index.ftlh b/central/src/main/resources/manage/index.ftlh
index 2cac44a..d56c9aa 100644
--- a/central/src/main/resources/manage/index.ftlh
+++ b/central/src/main/resources/manage/index.ftlh
@@ -14,14 +14,12 @@
<tr>
<th>IP</th>
<th>Method</th>
- <th>Provision</th>
<th>Actions</th>
</tr>
<#list peers as peer>
<tr>
<td>${peer.ipv4}<#if peer.ipv6??><br />${peer.ipv6}</#if></td>
<td>${peer.type}</td>
- <td>${peer.provisionStatus}</td>
<td><a href="/manage/edit?id=${peer.id}">Edit</a> |
<a href="/manage/show-configuration?id=${peer.id}">Example Conf</a></td>
</tr>
diff --git a/rpc-common/src/main/proto/agent.proto b/rpc-common/src/main/proto/agent.proto
index 585213e..1843917 100644
--- a/rpc-common/src/main/proto/agent.proto
+++ b/rpc-common/src/main/proto/agent.proto
@@ -6,42 +6,39 @@ option java_outer_classname = "AgentProto";
package moe.yuuta.dn42peering.agent;
service Agent {
- rpc ProvisionBGP (BGPRequest) returns (BGPReply) {}
- rpc ReloadBGP (BGPRequest) returns (BGPReply) {}
- rpc DeleteBGP (BGPRequest) returns (BGPReply) {}
-
- rpc ProvisionWG (WGRequest) returns (WGReply) {}
- rpc ReloadWG (WGRequest) returns (WGReply) {}
- rpc DeleteWG (WGRequest) returns (WGReply) {}
+ rpc Deploy (NodeConfig) returns (DeployResult) {}
}
-message BGPRequest {
- Node node = 1;
- uint64 id = 2;
- string asn = 3;
- bool mpbgp = 4;
- string ipv4 = 5;
- string ipv6 = 6;
- string device = 7;
+message DeployResult {
+ repeated uint64 successfulIDs = 1;
+ repeated uint64 failedIDs = 2;
}
-message BGPReply {
+message NodeConfig {
+ Node node = 1;
+ repeated BGPConfig bgps = 2;
+ repeated WireGuardConfig wgs = 3;
}
-message WGRequest {
- Node node = 1;
- uint64 id = 2;
- uint32 listenPort = 3;
- string endpoint = 4;
- string peerPubKey = 5;
- string selfPrivKey = 6;
- string selfPresharedSecret = 7;
- string peerIPv4 = 8;
- string peerIPv6 = 9;
+message BGPConfig {
+ uint64 id = 1;
+ uint64 asn = 2;
+ bool mpbgp = 3;
+ string ipv4 = 4;
+ string ipv6 = 5;
+ string interface = 6;
}
-message WGReply {
- string device = 1;
+message WireGuardConfig {
+ uint64 id = 1;
+ uint32 listenPort = 2;
+ string endpoint = 3;
+ string peerPubKey = 4;
+ string selfPrivKey = 5;
+ string selfPresharedSecret = 6;
+ string peerIPv4 = 7;
+ string peerIPv6 = 8;
+ string interface = 9;
}
message Node {
@@ -49,4 +46,5 @@ message Node {
string ipv4 = 2;
string ipv6 = 3;
string ipv6NonLL = 4;
+ uint64 asn = 5;
} \ No newline at end of file