aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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