aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-03-28 22:17:37 -0700
committerTrumeet <yuuta@yuuta.moe>2021-03-28 22:17:37 -0700
commitce7703ca44a8fb7a65cbffb7c258d043d7cd94e4 (patch)
tree62291bfa346c128f934599cb200ad0c77db30dd8
parent53354f03d0dc8342c35073881edf78452637d68b (diff)
downloaddn42peering-ce7703ca44a8fb7a65cbffb7c258d043d7cd94e4.tar
dn42peering-ce7703ca44a8fb7a65cbffb7c258d043d7cd94e4.tar.gz
dn42peering-ce7703ca44a8fb7a65cbffb7c258d043d7cd94e4.tar.bz2
dn42peering-ce7703ca44a8fb7a65cbffb7c258d043d7cd94e4.zip
refactor(central): move some common codes outside of ManageHandler
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java745
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java197
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManagementUI.java420
-rw-r--r--central/src/main/java/moe/yuuta/dn42peering/manage/ManagementVerify.java98
4 files changed, 739 insertions, 721 deletions
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 052f97d..b64bb13 100644
--- a/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java
+++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManageHandler.java
@@ -1,14 +1,7 @@
package moe.yuuta.dn42peering.manage;
-import com.wireguard.crypto.Key;
-import com.wireguard.crypto.KeyFormatException;
-import edazdarevic.commons.net.CIDRUtils;
-import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
-import io.vertx.core.Handler;
import io.vertx.core.Vertx;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.core.http.HttpHeaders;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
@@ -27,32 +20,25 @@ import io.vertx.json.schema.SchemaParser;
import io.vertx.json.schema.SchemaRouter;
import io.vertx.json.schema.SchemaRouterOptions;
import io.vertx.json.schema.common.dsl.ObjectSchemaBuilder;
-import moe.yuuta.dn42peering.provision.BGPRequestCommon;
-import moe.yuuta.dn42peering.provision.WGRequestCommon;
import moe.yuuta.dn42peering.asn.IASNService;
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.portal.FormException;
import moe.yuuta.dn42peering.portal.HTTPException;
import moe.yuuta.dn42peering.portal.ISubRouter;
import moe.yuuta.dn42peering.provision.IProvisionRemoteService;
import moe.yuuta.dn42peering.whois.IWhoisService;
-import moe.yuuta.dn42peering.whois.WhoisObject;
-import org.apache.commons.validator.routines.InetAddressValidator;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.io.IOException;
-import java.net.Inet6Address;
import java.util.*;
-import java.util.stream.Collectors;
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());
@@ -88,32 +74,15 @@ public class ManageHandler implements ISubRouter {
final String asn = ctx.user().principal().getString("username");
Future.<List<moe.yuuta.dn42peering.peer.Peer>>future(f ->
peerService.listUnderASN(asn, f))
- .<Buffer>compose(peers -> Future.future(f -> renderIndex(engine, asn, peers, f)))
- .onComplete(res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- }
- });
+ .onSuccess(peers -> renderIndex(engine, asn, peers, ctx))
+ .onFailure(ctx::fail);
});
router.get("/new")
.produces("text/html")
.handler(ctx -> {
final String asn = ctx.user().principal().getString("username");
- renderForm(engine, nodeService, true, asn, null, null, res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- res.cause().printStackTrace();
- ctx.fail(res.cause());
- }
- });
+ renderForm(engine, nodeService, true, asn, null, null, ctx);
});
final ObjectSchemaBuilder registerSchema = objectSchema()
@@ -147,36 +116,7 @@ public class ManageHandler implements ISubRouter {
peer.setAsn(asn);
return Future.succeededFuture(peer);
}).compose(peer ->
- Future.<WhoisObject>future(f -> whoisService.query(peer.getIpv4(), f))
- .compose(whoisObject -> {
- if (whoisObject == null ||
- !whoisObject.containsKey("origin") ||
- !whoisObject.get("origin").contains(asn)) {
- return Future.failedFuture(new FormException(peer,
- "The IPv4 address you specified does not have a route with your ASN."));
- }
- // Verify IPv6
- try {
- if (peer.getIpv6() != null && !peer.isIPv6LinkLocal()) {
- return Future.<WhoisObject>future(f -> whoisService.query(peer.getIpv6(), f))
- .compose(ipv6Whois -> {
- if (ipv6Whois == null ||
- !ipv6Whois.containsKey("origin") ||
- !ipv6Whois.get("origin").contains(asn)) {
- return Future.failedFuture(new FormException(peer,
- "The IPv6 address you specified does not have a route with your ASN."));
- } else {
- return Future.succeededFuture(peer);
- }
- });
- } else {
- // Do not check for IPv6 address.
- return Future.succeededFuture(peer);
- }
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- })
+ ManagementVerify.verifyAllOrThrow(whoisService, peer, asn)
).<Peer>compose(peer -> Future.future(f -> {
boolean needCheckIP6 = true;
try {
@@ -211,7 +151,7 @@ public class ManageHandler implements ISubRouter {
.putHeader("Location", "/manage")
.end();
provisionPeer(nodeService, provisionService, peer).onComplete(ar ->
- this.handleProvisionResult(peerService, peer, ar));
+ handleProvisionResult(peerService, peer, ar));
})
.onFailure(err -> {
if (err instanceof FormException) {
@@ -219,15 +159,7 @@ public class ManageHandler implements ISubRouter {
true, asn,
((Peer) ((FormException) err).data),
Arrays.asList(((FormException) err).errors),
- res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- }
- });
+ ctx);
} else {
if(!(err instanceof HTTPException)) logger.error("Cannot add peer.", err);
ctx.fail(err);
@@ -252,19 +184,9 @@ public class ManageHandler implements ISubRouter {
}
return Future.succeededFuture(peer);
})
- .<Buffer>compose(peer -> Future.future(f -> renderForm(engine, nodeService, false,
- asn, peer, null, f)))
- .onComplete(res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- if(!(res.cause() instanceof HTTPException)) logger.error(String.format("Cannot show peer %s", id),
- res.cause());
- ctx.fail(res.cause());
- }
- });
+ .onSuccess(peer -> Future.future(f -> renderForm(engine, nodeService, false,
+ asn, peer, null, ctx)))
+ .onFailure(ctx::fail);
});
router.post("/edit")
@@ -300,63 +222,17 @@ public class ManageHandler implements ISubRouter {
})
).compose(peer -> {
final Peer inPeer = peer.b;
- return Future.<WhoisObject>future(f -> whoisService.query(inPeer.getIpv4(), f))
- .compose(whoisObject -> {
- if (whoisObject == null ||
- !whoisObject.containsKey("origin") ||
- !whoisObject.get("origin").contains(asn)) {
- return Future.failedFuture(new FormException(inPeer,
- "The IPv4 address you specified does not have a route with your ASN."));
- }
- // Verify IPv6
- try {
- if (inPeer.getIpv6() != null && !inPeer.isIPv6LinkLocal()) {
- return Future.<WhoisObject>future(f -> whoisService.query(inPeer.getIpv6(), f))
- .compose(ipv6Whois -> {
- if (ipv6Whois == null ||
- !ipv6Whois.containsKey("origin") ||
- !ipv6Whois.get("origin").contains(asn)) {
- return Future.failedFuture(new FormException(inPeer,
- "The IPv6 address you specified does not have a route with your ASN."));
- } else {
- return Future.succeededFuture(peer);
- }
- });
- } else {
- // Do not check for IPv6 address.
- return Future.succeededFuture(peer);
- }
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- });
+ return ManagementVerify.verifyAllOrThrow(whoisService, inPeer, asn)
+ .compose(_peer -> Future.succeededFuture(peer));
}
).<Pair<Peer /* Existing */, Peer /* Input */>>compose(peer -> {
final Peer existingPeer = peer.a;
final Peer inPeer = peer.b;
- boolean needCheckIPv4Conflict;
- boolean needCheckIPv6Conflict;
-
- if (existingPeer.getType() != inPeer.getType()) {
- needCheckIPv4Conflict = true;
- needCheckIPv6Conflict = true;
- } else {
- needCheckIPv4Conflict =
- !Objects.equals(existingPeer.getIpv4(), inPeer.getIpv4());
- needCheckIPv6Conflict =
- !Objects.equals(existingPeer.getIpv6(), inPeer.getIpv6());
- if (inPeer.getIpv6() == null) needCheckIPv6Conflict = false;
- else {
- try {
- if(inPeer.isIPv6LinkLocal())
- needCheckIPv6Conflict = false;
- } catch (IOException ignored) {}
- }
- }
- final boolean nc6 = needCheckIPv6Conflict;
+ final boolean needCheckIPv4Conflict = ManagementVerify.determineIfNeedCheckIPv4(existingPeer, inPeer);
+ final boolean needCheckIPv6Conflict = ManagementVerify.determineIfNeedCheckIPv6(existingPeer, inPeer);
return Future.future(f -> peerService.isIPConflict(inPeer.getType(),
needCheckIPv4Conflict ? inPeer.getIpv4() : null,
- nc6 ? inPeer.getIpv6() : null,
+ needCheckIPv6Conflict ? inPeer.getIpv6() : null,
ar -> {
if (ar.succeeded()) {
if (ar.result()) {
@@ -383,7 +259,7 @@ public class ManageHandler implements ISubRouter {
final Peer existingPeer = pair.a;
final Peer inPeer = pair.b;
reloadPeer(nodeService, provisionService, existingPeer, inPeer).onComplete(ar ->
- this.handleProvisionResult(peerService, inPeer, ar));
+ handleProvisionResult(peerService, inPeer, ar));
})
.onFailure(err -> {
if (err instanceof FormException) {
@@ -401,15 +277,7 @@ public class ManageHandler implements ISubRouter {
asn,
peer,
Arrays.asList(((FormException) err).errors),
- res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- ctx.fail(res.cause());
- }
- });
+ ctx);
} else {
if(!(err instanceof HTTPException)) logger.error("Cannot edit peer.", err);
ctx.fail(err);
@@ -449,16 +317,7 @@ public class ManageHandler implements ISubRouter {
.produces("text/html")
.handler(ctx -> {
final String asn = ctx.user().principal().getString("username");
- renderChangepw(engine, asn, null, res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- res.cause().printStackTrace();
- ctx.fail(res.cause());
- }
- });
+ renderChangepw(engine, asn, null, ctx);
});
router.post("/change-password")
@@ -499,16 +358,7 @@ public class ManageHandler implements ISubRouter {
if (err instanceof FormException) {
renderChangepw(engine, asn,
Arrays.asList(((FormException) err).errors),
- res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- res.cause().printStackTrace();
- ctx.fail(res.cause());
- }
- });
+ ctx);
} else {
if(!(err instanceof HTTPException)) logger.error("Cannot change password.", err);
ctx.fail(err);
@@ -520,16 +370,7 @@ public class ManageHandler implements ISubRouter {
.produces("text/html")
.handler(ctx -> {
final String asn = ctx.user().principal().getString("username");
- renderDA(engine, asn, null, res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- res.cause().printStackTrace();
- ctx.fail(res.cause());
- }
- });
+ renderDA(engine, asn, null, ctx);
});
router.post("/delete-account")
@@ -558,16 +399,7 @@ public class ManageHandler implements ISubRouter {
if (err instanceof FormException) {
renderDA(engine, asn,
Arrays.asList(((FormException) err).errors),
- res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- res.cause().printStackTrace();
- ctx.fail(res.cause());
- }
- });
+ ctx);
} else {
logger.error("Cannot delete account.", err);
ctx.fail(err);
@@ -592,539 +424,10 @@ public class ManageHandler implements ISubRouter {
}
return Future.succeededFuture(peer);
})
- .compose(peer -> renderShowConfig(nodeService, engine, peer))
- .onComplete(res -> {
- if (res.succeeded()) {
- ctx.response()
- .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
- .end(res.result());
- } else {
- if(!(res.cause() instanceof HTTPException))
- logger.error(String.format("Cannot render show configuration %s", id),
- res.cause());
- ctx.fail(res.cause());
- }
- });
+ .onSuccess(peer -> renderShowConfig(nodeService, engine, peer, ctx))
+ .onFailure(ctx::fail);
});
return router;
}
-
- @Nonnull
- private Future<Peer> parseForm(@Nonnull INodeService nodeService,
- @Nonnull JsonObject form) {
- // Parse form
- int nodeId = -1;
- if (form.containsKey("node")) {
- try {
- nodeId = Integer.parseInt(form.getString("node"));
- } catch (NumberFormatException ignored) {
- }
- if(nodeId == -1) {
- return Future.failedFuture(new FormException("The node selection is invalid."));
- }
- }
-
- int n = nodeId;
- return Future.<Node>future(f -> nodeService.getNode(n, f))
- .compose(node -> {
- try {
- final List<String> errors = new ArrayList<>(10);
- if(node == null) {
- errors.add("The node selection is invalid.");
- }
- Peer.VPNType type = null;
- if (form.containsKey("vpn")) {
- final String rawVPN = form.getString("vpn");
- if (rawVPN == null) {
- errors.add("Tunneling type is not specified.");
- } else
- switch (rawVPN) {
- case "wg":
- type = Peer.VPNType.WIREGUARD;
- break;
- default:
- errors.add("Tunneling type is unexpected.");
- break;
- }
- } else {
- errors.add("Tunneling type is not specified.");
- }
-
- String ipv4 = null;
- if (form.containsKey("ipv4")) {
- ipv4 = form.getString("ipv4");
- if (ipv4 == null || ipv4.isEmpty()) {
- errors.add("IPv4 address is not specified.");
- ipv4 = null; // Non-null but empty values could cause problems.
- } else {
- if (InetAddressValidator.getInstance().isValidInet4Address(ipv4)) {
- if (!new CIDRUtils("172.20.0.0/14").isInRange(ipv4)) {
- errors.add("IPv4 address is illegal. It must be a dn42 IPv4 address (172.20.x.x to 172.23.x.x).");
- }
- } else
- errors.add("IPv4 address is illegal. Cannot parse your address.");
- }
- } else {
- errors.add("IPv4 address is not specified.");
- }
-
- String ipv6 = null;
- if (form.containsKey("ipv6")) {
- ipv6 = form.getString("ipv6");
- if (ipv6 != null && !ipv6.isEmpty()) {
- if (InetAddressValidator.getInstance().isValidInet6Address(ipv6)) {
- if (!new CIDRUtils("fd00::/8").isInRange(ipv6) &&
- !Inet6Address.getByName(ipv6).isLinkLocalAddress()) {
- errors.add("IPv6 address is illegal. It must be a dn42 or link-local IPv6 address.");
- }
- ipv6 = ipv6.replaceAll("((?::0\\b){2,}):?(?!\\S*\\b\\1:0\\b)(\\S*)", "::$2");
- } else
- errors.add("IPv6 address is illegal. Cannot parse your address.");
- } else {
- ipv6 = null; // Non-null but empty values could cause problems.
- }
- }
-
- boolean mpbgp = false;
- if (form.containsKey("mpbgp")) {
- if (ipv6 == null) {
- errors.add("MP-BGP cannot be enabled if you do not have a valid IPv6 address.");
- } else {
- mpbgp = true;
- }
- }
-
- String wgEndpoint = null;
- boolean wgEndpointCorrect = false;
- if (form.containsKey("wg_endpoint")) {
- if (type == Peer.VPNType.WIREGUARD) {
- wgEndpoint = form.getString("wg_endpoint");
- if (wgEndpoint != null && !wgEndpoint.isEmpty()) {
- if (InetAddressValidator.getInstance().isValidInet4Address(wgEndpoint)) {
- if (new CIDRUtils("10.0.0.0/8").isInRange(wgEndpoint) ||
- new CIDRUtils("192.168.0.0/16").isInRange(wgEndpoint) ||
- new CIDRUtils("172.16.0.0/23").isInRange(wgEndpoint)) {
- errors.add("WireGuard EndPoint is illegal. It must not be an internal address.");
- } else {
- wgEndpointCorrect = true;
- }
- } else
- errors.add("WireGuard EndPoint is illegal. Cannot parse your address.");
- } else {
- wgEndpoint = null; // Non-null but empty values could cause problems.
- }
- } else {
- errors.add("WireGuard tunneling is not selected but WireGuard Endpoint configuration appears.");
- }
- }
-
- Integer wgEndpointPort = null;
- if (form.containsKey("wg_endpoint_port")) {
- if (type == Peer.VPNType.WIREGUARD) {
- final String rawPort = form.getString("wg_endpoint_port");
- if(rawPort != null && !rawPort.isEmpty()) {
- if (wgEndpointCorrect) {
- try {
- wgEndpointPort = Integer.parseInt(rawPort);
- if (wgEndpointPort < 0 || wgEndpointPort > 65535) {
- errors.add("WireGuard EndPoint port must be in UDP port range.");
- }
- } catch (NumberFormatException | NullPointerException ignored) {
- errors.add("WireGuard EndPoint port is not valid. It must be a number.");
- }
- } else {
- errors.add("WireGuard EndPoint IP is not specified or invalid, but port is specified.");
- }
- }
- } else {
- errors.add("WireGuard tunneling is not selected but WireGuard Endpoint configuration appears.");
- }
- }
-
- // When user specified the endpoint without the port.
- if(type == Peer.VPNType.WIREGUARD &&
- wgEndpointCorrect &&
- wgEndpointPort == null) {
- errors.add("WireGuard EndPoint IP is specified, but the port is missing.");
- }
-
- String wgPubKey = null;
- if (form.containsKey("wg_pubkey")) {
- if (type == Peer.VPNType.WIREGUARD) {
- wgPubKey = form.getString("wg_pubkey");
- if (wgPubKey == null || wgPubKey.isEmpty()) {
- errors.add("WireGuard public key is not specified.");
- wgPubKey = null; // Non-null but empty values could cause problems.
- } else {
- try {
- Key.fromBase64(wgPubKey);
- } catch (KeyFormatException e) {
- errors.add("WireGuard public key is not valid.");
- }
- }
- } else {
- errors.add("WireGuard tunneling is not selected but WireGuard public key appears.");
- }
- } else {
- if (type == Peer.VPNType.WIREGUARD) {
- errors.add("WireGuard public key is not specified.");
- }
- }
-
- if(node != null && !node.getSupportedVPNTypes().contains(type)) {
- errors.add(String.format("Node %s does not support VPN type %s.", node.getName(),
- type));
- }
-
- Peer peer;
- if (type == Peer.VPNType.WIREGUARD) {
- peer = new Peer(ipv4, ipv6, wgEndpoint, wgEndpointPort, wgPubKey, mpbgp, n);
- } else {
- peer = new Peer(
- -1,
- type,
- null, /* ASN: To be filled later */
- ipv4,
- ipv6,
- wgEndpoint,
- wgEndpointPort,
- null, /* Self public key: Generate later if needed */
- null, /* Self private key: Generate later if needed */
- wgPubKey,
- null /* Preshared Secret: Generate later if needed */,
- ProvisionStatus.NOT_PROVISIONED,
- mpbgp,
- n
- );
- }
- if(errors.isEmpty()) {
- return Future.succeededFuture(peer);
- } else {
- return Future.failedFuture(new FormException(peer, errors.toArray(new String[]{})));
- }
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- });
- }
-
- private void renderIndex(@Nonnull TemplateEngine engine,
- @Nonnull String asn, @Nonnull List<Peer> peers,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- final Map<String, Object> root = new HashMap<>();
- root.put("asn", asn);
- root.put("peers", peers.stream()
- .map(peer -> {
- final Map<String, Object> map = new HashMap<>();
- map.put("id", peer.getId());
- map.put("ipv4", peer.getIpv4());
- map.put("ipv6", peer.getIpv6());
- map.put("type", peer.getType());
- map.put("provisionStatus", peer.getProvisionStatus());
- return map;
- })
- .collect(Collectors.toList()));
- engine.render(root, "manage/index.ftlh", handler);
- }
-
- @SuppressWarnings("unchecked")
- private void renderForm(@Nonnull TemplateEngine engine,
- @Nonnull INodeService nodeService,
- boolean newForm,
- @Nonnull String asn, @Nullable Peer peer, @Nullable List<String> errors,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- Future.future(nodeService::listNodes)
- .compose(list -> {
- final Map<String, Object> root = new HashMap<>();
- root.put("asn", asn);
- root.put("nodes", list.stream()
- .map(node -> {
- final Map<String, Object> map = new HashMap<>(10);
- map.put("id", node.getId());
- map.put("name", node.getName());
- map.put("public_ip", node.getPublicIp());
- map.put("notice", node.getNotice());
- map.put("asn", node.getAsn());
- map.put("vpn_types", node.getSupportedVPNTypes());
- return map;
- })
- .collect(Collectors.toList()));
- if (peer != null) {
- root.put("ipv4", peer.getIpv4());
- root.put("ipv6", peer.getIpv6());
- switch (peer.getType()) {
- case WIREGUARD:
- root.put("typeWireguard", true);
- break;
- }
- root.put("wgEndpoint", peer.getWgEndpoint());
- root.put("wgEndpointPort", peer.getWgEndpointPort());
- root.put("wgPubkey", peer.getWgPeerPubkey());
- root.put("mpbgp", peer.isMpbgp());
- root.put("node_checked", peer.getNode());
- root.put("id", peer.getId());
- } else {
- root.put("typeWireguard", true);
- root.put("mpbgp", false);
- root.put("node_checked", ((List<Map<String, Object>>)root.get("nodes")).get(0).get("id"));
- }
- if(!newForm && peer != null)
- root.put("action", "/manage/edit?id=" + peer.getId());
- else
- root.put("action", "/manage/new");
- root.put("errors", errors);
- return engine.render(root, newForm ? "manage/new.ftlh" : "manage/edit.ftlh");
- })
- .onComplete(handler);
- }
-
- private void renderChangepw(@Nonnull TemplateEngine engine,
- @Nonnull String asn, @Nullable List<String> errors,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- final Map<String, Object> root = new HashMap<>();
- root.put("asn", asn);
- root.put("errors", errors);
- engine.render(root, "manage/changepw.ftlh", handler);
- }
-
- private void renderDA(@Nonnull TemplateEngine engine,
- @Nonnull String asn, @Nullable List<String> errors,
- @Nonnull Handler<AsyncResult<Buffer>> handler) {
- final Map<String, Object> root = new HashMap<>();
- root.put("asn", asn);
- root.put("errors", errors);
- engine.render(root, "manage/delete.ftlh", handler);
- }
-
- @Nonnull
- private Future<Buffer> renderShowConfig(@Nonnull INodeService nodeService,
- @Nonnull TemplateEngine engine,
- @Nonnull Peer peer) {
- return Future.<Node>future(f -> nodeService.getNode(peer.getNode(), f))
- .compose(node -> {
- final Map<String, Object> root = new HashMap<>();
- root.put("ipv4", peer.getIpv4());
- root.put("ipv6", peer.getIpv6());
- switch (peer.getType()) {
- case WIREGUARD:
- root.put("typeWireguard", true);
- break;
- }
- root.put("wgPort", peer.calcWireGuardPort());
- root.put("wgEndpoint", peer.getWgEndpoint());
- root.put("wgEndpointPort", peer.getWgEndpointPort());
- root.put("wgPresharedSecret", peer.getWgPresharedSecret());
- root.put("wgSelfPubkey", peer.getWgSelfPubkey());
- root.put("mpbgp", peer.isMpbgp());
- root.put("peer_type", peer.getType());
- root.put("peer_ipv4", peer.getIpv4());
- root.put("peer_ipv6", peer.getIpv6());
- if(peer.getWgEndpoint() != null) {
- root.put("peer_wg_listen_port", peer.getWgEndpointPort());
- }
-
- if(node == null) {
- root.put("ipv4", "This node is currently down! Edit the peer to choose another one.");
- root.put("ipv6", "This node is currently down! Edit the peer to choose another one.");
- root.put("asn", "This node is currently down! Edit the peer to choose another one.");
- root.put("endpoint", "This node is currently down! Edit the peer to choose another one.");
- root.put("show_example_config", false);
- } else {
- root.put("show_example_config", true);
- root.put("ipv4", node.getDn42Ip4());
- try {
- if(peer.isIPv6LinkLocal()) {
- root.put("ipv6", node.getDn42Ip6());
- root.put("peer_link_local", true);
- } else {
- root.put("ipv6", node.getDn42Ip6NonLL());
- root.put("peer_link_local", false);
- }
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- root.put("asn", node.getAsn().substring(2));
- root.put("endpoint", node.getPublicIp());
- }
-
- return engine.render(root, "manage/showconf.ftlh");
- });
-
- }
-
- @Nonnull
- private 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 local IP addresses.
- // However, switch between link local addresses and real IPv6 addresses require the change of
- // local v6 address. Therefore, in such cases, we have to do a full re-provision.
- if(canReload && // Only check if no other factors prevent us from reloading.
- inPeer.getType() == Peer.VPNType.WIREGUARD &&
- existingPeer.getType() == Peer.VPNType.WIREGUARD) {
- try {
- final boolean existingLL = existingPeer.isIPv6LinkLocal();
- final boolean newLL = inPeer.isIPv6LinkLocal();
- if(existingLL != newLL) {
- canReload = false;
- }
- } catch (IOException e) {
- return Future.failedFuture(e);
- }
- }
- // 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;
- }
-
- private 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
- private 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));
- });
- }
-
- private 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/manage/ManagementProvision.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java
new file mode 100644
index 0000000..5c9b531
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementProvision.java
@@ -0,0 +1,197 @@
+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 local IP addresses.
+ // However, switch between link local addresses and real IPv6 addresses require the change of
+ // local v6 address. Therefore, in such cases, we have to do a full re-provision.
+ if(canReload && // Only check if no other factors prevent us from reloading.
+ inPeer.getType() == Peer.VPNType.WIREGUARD &&
+ existingPeer.getType() == Peer.VPNType.WIREGUARD) {
+ try {
+ final boolean existingLL = existingPeer.isIPv6LinkLocal();
+ final boolean newLL = inPeer.isIPv6LinkLocal();
+ if(existingLL != newLL) {
+ canReload = false;
+ }
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ }
+ // 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/manage/ManagementUI.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementUI.java
new file mode 100644
index 0000000..641df19
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementUI.java
@@ -0,0 +1,420 @@
+package moe.yuuta.dn42peering.manage;
+
+import com.wireguard.crypto.Key;
+import com.wireguard.crypto.KeyFormatException;
+import edazdarevic.commons.net.CIDRUtils;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.common.template.TemplateEngine;
+import moe.yuuta.dn42peering.node.INodeService;
+import moe.yuuta.dn42peering.node.Node;
+import moe.yuuta.dn42peering.peer.Peer;
+import moe.yuuta.dn42peering.peer.ProvisionStatus;
+import moe.yuuta.dn42peering.portal.FormException;
+import org.apache.commons.validator.routines.InetAddressValidator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+class ManagementUI {
+ private static Handler<AsyncResult<Buffer>> getGeneralRenderingHandler(@Nonnull RoutingContext ctx) {
+ return res -> {
+ if (res.succeeded()) {
+ ctx.response()
+ .putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
+ .end(res.result());
+ } else {
+ ctx.fail(res.cause());
+ }
+ };
+ }
+
+ public static void renderIndex(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nonnull List<Peer> peers,
+ @Nonnull RoutingContext ctx) {
+ renderIndex(engine, asn, peers, getGeneralRenderingHandler(ctx));
+ }
+
+ public static void renderIndex(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nonnull List<Peer> peers,
+ @Nonnull Handler<AsyncResult<Buffer>> handler) {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("asn", asn);
+ root.put("peers", peers.stream()
+ .map(peer -> {
+ final Map<String, Object> map = new HashMap<>();
+ map.put("id", peer.getId());
+ map.put("ipv4", peer.getIpv4());
+ map.put("ipv6", peer.getIpv6());
+ map.put("type", peer.getType());
+ map.put("provisionStatus", peer.getProvisionStatus());
+ return map;
+ })
+ .collect(Collectors.toList()));
+ engine.render(root, "manage/index.ftlh", handler);
+ }
+
+ public static void renderForm(@Nonnull TemplateEngine engine,
+ @Nonnull INodeService nodeService,
+ boolean newForm,
+ @Nonnull String asn, @Nullable Peer peer, @Nullable List<String> errors,
+ @Nonnull RoutingContext ctx) {
+ renderForm(engine, nodeService, newForm, asn, peer, errors, getGeneralRenderingHandler(ctx));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void renderForm(@Nonnull TemplateEngine engine,
+ @Nonnull INodeService nodeService,
+ boolean newForm,
+ @Nonnull String asn, @Nullable Peer peer, @Nullable List<String> errors,
+ @Nonnull Handler<AsyncResult<Buffer>> handler) {
+ Future.future(nodeService::listNodes)
+ .compose(list -> {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("asn", asn);
+ root.put("nodes", list.stream()
+ .map(node -> {
+ final Map<String, Object> map = new HashMap<>(10);
+ map.put("id", node.getId());
+ map.put("name", node.getName());
+ map.put("public_ip", node.getPublicIp());
+ map.put("notice", node.getNotice());
+ map.put("asn", node.getAsn());
+ map.put("vpn_types", node.getSupportedVPNTypes());
+ return map;
+ })
+ .collect(Collectors.toList()));
+ if (peer != null) {
+ root.put("ipv4", peer.getIpv4());
+ root.put("ipv6", peer.getIpv6());
+ switch (peer.getType()) {
+ case WIREGUARD:
+ root.put("typeWireguard", true);
+ break;
+ }
+ root.put("wgEndpoint", peer.getWgEndpoint());
+ root.put("wgEndpointPort", peer.getWgEndpointPort());
+ root.put("wgPubkey", peer.getWgPeerPubkey());
+ root.put("mpbgp", peer.isMpbgp());
+ root.put("node_checked", peer.getNode());
+ root.put("id", peer.getId());
+ } else {
+ root.put("typeWireguard", true);
+ root.put("mpbgp", false);
+ root.put("node_checked", ((List<Map<String, Object>>)root.get("nodes")).get(0).get("id"));
+ }
+ if(!newForm && peer != null)
+ root.put("action", "/manage/edit?id=" + peer.getId());
+ else
+ root.put("action", "/manage/new");
+ root.put("errors", errors);
+ return engine.render(root, newForm ? "manage/new.ftlh" : "manage/edit.ftlh");
+ })
+ .onComplete(handler);
+ }
+
+
+ public static void renderChangepw(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nullable List<String> errors,
+ @Nonnull RoutingContext ctx) {
+ renderChangepw(engine, asn, errors, getGeneralRenderingHandler(ctx));
+ }
+
+ public static void renderChangepw(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nullable List<String> errors,
+ @Nonnull Handler<AsyncResult<Buffer>> handler) {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("asn", asn);
+ root.put("errors", errors);
+ engine.render(root, "manage/changepw.ftlh", handler);
+ }
+
+ public static void renderDA(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nullable List<String> errors,
+ @Nonnull RoutingContext ctx) {
+ renderDA(engine, asn, errors, getGeneralRenderingHandler(ctx));
+ }
+
+ public static void renderDA(@Nonnull TemplateEngine engine,
+ @Nonnull String asn, @Nullable List<String> errors,
+ @Nonnull Handler<AsyncResult<Buffer>> handler) {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("asn", asn);
+ root.put("errors", errors);
+ engine.render(root, "manage/delete.ftlh", handler);
+ }
+
+ public static void renderShowConfig(@Nonnull INodeService nodeService,
+ @Nonnull TemplateEngine engine,
+ @Nonnull Peer peer,
+ @Nonnull RoutingContext ctx) {
+ renderShowConfig(nodeService, engine, peer).onComplete(getGeneralRenderingHandler(ctx));
+ }
+
+ @Nonnull
+ public static Future<Buffer> renderShowConfig(@Nonnull INodeService nodeService,
+ @Nonnull TemplateEngine engine,
+ @Nonnull Peer peer) {
+ return Future.<Node>future(f -> nodeService.getNode(peer.getNode(), f))
+ .compose(node -> {
+ final Map<String, Object> root = new HashMap<>();
+ root.put("ipv4", peer.getIpv4());
+ root.put("ipv6", peer.getIpv6());
+ switch (peer.getType()) {
+ case WIREGUARD:
+ root.put("typeWireguard", true);
+ break;
+ }
+ root.put("wgPort", peer.calcWireGuardPort());
+ root.put("wgEndpoint", peer.getWgEndpoint());
+ root.put("wgEndpointPort", peer.getWgEndpointPort());
+ root.put("wgPresharedSecret", peer.getWgPresharedSecret());
+ root.put("wgSelfPubkey", peer.getWgSelfPubkey());
+ root.put("mpbgp", peer.isMpbgp());
+ root.put("peer_type", peer.getType());
+ root.put("peer_ipv4", peer.getIpv4());
+ root.put("peer_ipv6", peer.getIpv6());
+ if(peer.getWgEndpoint() != null) {
+ root.put("peer_wg_listen_port", peer.getWgEndpointPort());
+ }
+
+ if(node == null) {
+ root.put("ipv4", "This node is currently down! Edit the peer to choose another one.");
+ root.put("ipv6", "This node is currently down! Edit the peer to choose another one.");
+ root.put("asn", "This node is currently down! Edit the peer to choose another one.");
+ root.put("endpoint", "This node is currently down! Edit the peer to choose another one.");
+ root.put("show_example_config", false);
+ } else {
+ root.put("show_example_config", true);
+ root.put("ipv4", node.getDn42Ip4());
+ try {
+ if(peer.isIPv6LinkLocal()) {
+ root.put("ipv6", node.getDn42Ip6());
+ root.put("peer_link_local", true);
+ } else {
+ root.put("ipv6", node.getDn42Ip6NonLL());
+ root.put("peer_link_local", false);
+ }
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ root.put("asn", node.getAsn().substring(2));
+ root.put("endpoint", node.getPublicIp());
+ }
+
+ return engine.render(root, "manage/showconf.ftlh");
+ });
+
+ }
+
+ @Nonnull
+ public static Future<Peer> parseForm(@Nonnull INodeService nodeService,
+ @Nonnull JsonObject form) {
+ // Parse form
+ int nodeId = -1;
+ if (form.containsKey("node")) {
+ try {
+ nodeId = Integer.parseInt(form.getString("node"));
+ } catch (NumberFormatException ignored) {
+ }
+ if(nodeId == -1) {
+ return Future.failedFuture(new FormException("The node selection is invalid."));
+ }
+ }
+
+ int n = nodeId;
+ return Future.<Node>future(f -> nodeService.getNode(n, f))
+ .compose(node -> {
+ try {
+ final List<String> errors = new ArrayList<>(10);
+ if(node == null) {
+ errors.add("The node selection is invalid.");
+ }
+ Peer.VPNType type = null;
+ if (form.containsKey("vpn")) {
+ final String rawVPN = form.getString("vpn");
+ if (rawVPN == null) {
+ errors.add("Tunneling type is not specified.");
+ } else
+ switch (rawVPN) {
+ case "wg":
+ type = Peer.VPNType.WIREGUARD;
+ break;
+ default:
+ errors.add("Tunneling type is unexpected.");
+ break;
+ }
+ } else {
+ errors.add("Tunneling type is not specified.");
+ }
+
+ String ipv4 = null;
+ if (form.containsKey("ipv4")) {
+ ipv4 = form.getString("ipv4");
+ if (ipv4 == null || ipv4.isEmpty()) {
+ errors.add("IPv4 address is not specified.");
+ ipv4 = null; // Non-null but empty values could cause problems.
+ } else {
+ if (InetAddressValidator.getInstance().isValidInet4Address(ipv4)) {
+ if (!new CIDRUtils("172.20.0.0/14").isInRange(ipv4)) {
+ errors.add("IPv4 address is illegal. It must be a dn42 IPv4 address (172.20.x.x to 172.23.x.x).");
+ }
+ } else
+ errors.add("IPv4 address is illegal. Cannot parse your address.");
+ }
+ } else {
+ errors.add("IPv4 address is not specified.");
+ }
+
+ String ipv6 = null;
+ if (form.containsKey("ipv6")) {
+ ipv6 = form.getString("ipv6");
+ if (ipv6 != null && !ipv6.isEmpty()) {
+ if (InetAddressValidator.getInstance().isValidInet6Address(ipv6)) {
+ if (!new CIDRUtils("fd00::/8").isInRange(ipv6) &&
+ !Inet6Address.getByName(ipv6).isLinkLocalAddress()) {
+ errors.add("IPv6 address is illegal. It must be a dn42 or link-local IPv6 address.");
+ }
+ ipv6 = ipv6.replaceAll("((?::0\\b){2,}):?(?!\\S*\\b\\1:0\\b)(\\S*)", "::$2");
+ } else
+ errors.add("IPv6 address is illegal. Cannot parse your address.");
+ } else {
+ ipv6 = null; // Non-null but empty values could cause problems.
+ }
+ }
+
+ boolean mpbgp = false;
+ if (form.containsKey("mpbgp")) {
+ if (ipv6 == null) {
+ errors.add("MP-BGP cannot be enabled if you do not have a valid IPv6 address.");
+ } else {
+ mpbgp = true;
+ }
+ }
+
+ String wgEndpoint = null;
+ boolean wgEndpointCorrect = false;
+ if (form.containsKey("wg_endpoint")) {
+ if (type == Peer.VPNType.WIREGUARD) {
+ wgEndpoint = form.getString("wg_endpoint");
+ if (wgEndpoint != null && !wgEndpoint.isEmpty()) {
+ if (InetAddressValidator.getInstance().isValidInet4Address(wgEndpoint)) {
+ if (new CIDRUtils("10.0.0.0/8").isInRange(wgEndpoint) ||
+ new CIDRUtils("192.168.0.0/16").isInRange(wgEndpoint) ||
+ new CIDRUtils("172.16.0.0/23").isInRange(wgEndpoint)) {
+ errors.add("WireGuard EndPoint is illegal. It must not be an internal address.");
+ } else {
+ wgEndpointCorrect = true;
+ }
+ } else
+ errors.add("WireGuard EndPoint is illegal. Cannot parse your address.");
+ } else {
+ wgEndpoint = null; // Non-null but empty values could cause problems.
+ }
+ } else {
+ errors.add("WireGuard tunneling is not selected but WireGuard Endpoint configuration appears.");
+ }
+ }
+
+ Integer wgEndpointPort = null;
+ if (form.containsKey("wg_endpoint_port")) {
+ if (type == Peer.VPNType.WIREGUARD) {
+ final String rawPort = form.getString("wg_endpoint_port");
+ if(rawPort != null && !rawPort.isEmpty()) {
+ if (wgEndpointCorrect) {
+ try {
+ wgEndpointPort = Integer.parseInt(rawPort);
+ if (wgEndpointPort < 0 || wgEndpointPort > 65535) {
+ errors.add("WireGuard EndPoint port must be in UDP port range.");
+ }
+ } catch (NumberFormatException | NullPointerException ignored) {
+ errors.add("WireGuard EndPoint port is not valid. It must be a number.");
+ }
+ } else {
+ errors.add("WireGuard EndPoint IP is not specified or invalid, but port is specified.");
+ }
+ }
+ } else {
+ errors.add("WireGuard tunneling is not selected but WireGuard Endpoint configuration appears.");
+ }
+ }
+
+ // When user specified the endpoint without the port.
+ if(type == Peer.VPNType.WIREGUARD &&
+ wgEndpointCorrect &&
+ wgEndpointPort == null) {
+ errors.add("WireGuard EndPoint IP is specified, but the port is missing.");
+ }
+
+ String wgPubKey = null;
+ if (form.containsKey("wg_pubkey")) {
+ if (type == Peer.VPNType.WIREGUARD) {
+ wgPubKey = form.getString("wg_pubkey");
+ if (wgPubKey == null || wgPubKey.isEmpty()) {
+ errors.add("WireGuard public key is not specified.");
+ wgPubKey = null; // Non-null but empty values could cause problems.
+ } else {
+ try {
+ Key.fromBase64(wgPubKey);
+ } catch (KeyFormatException e) {
+ errors.add("WireGuard public key is not valid.");
+ }
+ }
+ } else {
+ errors.add("WireGuard tunneling is not selected but WireGuard public key appears.");
+ }
+ } else {
+ if (type == Peer.VPNType.WIREGUARD) {
+ errors.add("WireGuard public key is not specified.");
+ }
+ }
+
+ if(node != null && !node.getSupportedVPNTypes().contains(type)) {
+ errors.add(String.format("Node %s does not support VPN type %s.", node.getName(),
+ type));
+ }
+
+ Peer peer;
+ if (type == Peer.VPNType.WIREGUARD) {
+ peer = new Peer(ipv4, ipv6, wgEndpoint, wgEndpointPort, wgPubKey, mpbgp, n);
+ } else {
+ peer = new Peer(
+ -1,
+ type,
+ null, /* ASN: To be filled later */
+ ipv4,
+ ipv6,
+ wgEndpoint,
+ wgEndpointPort,
+ null, /* Self public key: Generate later if needed */
+ null, /* Self private key: Generate later if needed */
+ wgPubKey,
+ null /* Preshared Secret: Generate later if needed */,
+ ProvisionStatus.NOT_PROVISIONED,
+ mpbgp,
+ n
+ );
+ }
+ if(errors.isEmpty()) {
+ return Future.succeededFuture(peer);
+ } else {
+ return Future.failedFuture(new FormException(peer, errors.toArray(new String[]{})));
+ }
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ });
+ }
+}
diff --git a/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementVerify.java b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementVerify.java
new file mode 100644
index 0000000..28de0d9
--- /dev/null
+++ b/central/src/main/java/moe/yuuta/dn42peering/manage/ManagementVerify.java
@@ -0,0 +1,98 @@
+package moe.yuuta.dn42peering.manage;
+
+import io.vertx.core.CompositeFuture;
+import io.vertx.core.Future;
+import moe.yuuta.dn42peering.peer.Peer;
+import moe.yuuta.dn42peering.portal.FormException;
+import moe.yuuta.dn42peering.whois.IWhoisService;
+import moe.yuuta.dn42peering.whois.WhoisObject;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Objects;
+
+class ManagementVerify {
+ public static Future<Peer> verifyIPv4RouteOrThrow(@Nonnull IWhoisService whoisService,
+ @Nonnull Peer peer,
+ @Nonnull String asn) {
+ return Future.<WhoisObject>future(f -> whoisService.query(peer.getIpv4(), f))
+ .compose(whoisObject -> {
+ if (whoisObject == null ||
+ !whoisObject.containsKey("origin") ||
+ !whoisObject.get("origin").contains(asn)) {
+ return Future.failedFuture(new FormException(peer,
+ "The IPv4 address you specified does not have a route with your ASN."));
+ } else {
+ return Future.succeededFuture(peer);
+ }
+ });
+ }
+
+ public static Future<Peer> verifyIPv6RouteOrThrow(@Nonnull IWhoisService whoisService,
+ @Nonnull Peer peer,
+ @Nonnull String asn) {
+ try {
+ if (peer.getIpv6() != null && !peer.isIPv6LinkLocal()) {
+ return Future.<WhoisObject>future(f -> whoisService.query(peer.getIpv6(), f))
+ .compose(ipv6Whois -> {
+ if (ipv6Whois == null ||
+ !ipv6Whois.containsKey("origin") ||
+ !ipv6Whois.get("origin").contains(asn)) {
+ return Future.failedFuture(new FormException(peer,
+ "The IPv6 address you specified does not have a route with your ASN."));
+ } else {
+ return Future.succeededFuture(peer);
+ }
+ });
+ } else {
+ // Do not check for IPv6 address.
+ return Future.succeededFuture(peer);
+ }
+ } catch (IOException e) {
+ return Future.failedFuture(e);
+ }
+ }
+
+ public static Future<Peer> verifyAllOrThrow(@Nonnull IWhoisService whoisService,
+ @Nonnull Peer peer,
+ @Nonnull String asn) {
+ return CompositeFuture.all(verifyIPv4RouteOrThrow(whoisService, peer, asn),
+ verifyIPv6RouteOrThrow(whoisService, peer, asn))
+ .compose(_res -> Future.succeededFuture(peer));
+ }
+
+ public static boolean determineIfNeedCheckIPv4(@Nonnull Peer existingPeer,
+ @Nonnull Peer inPeer) {
+ boolean needCheckIPv4Conflict;
+
+ if (existingPeer.getType() != inPeer.getType()) {
+ needCheckIPv4Conflict = true;
+ } else {
+ needCheckIPv4Conflict =
+ !Objects.equals(existingPeer.getIpv4(), inPeer.getIpv4());
+ }
+
+ return needCheckIPv4Conflict;
+ }
+
+ public static boolean determineIfNeedCheckIPv6(@Nonnull Peer existingPeer,
+ @Nonnull Peer inPeer) {
+ boolean needCheckIPv6Conflict;
+
+ if (existingPeer.getType() != inPeer.getType()) {
+ needCheckIPv6Conflict = true;
+ } else {
+ needCheckIPv6Conflict =
+ !Objects.equals(existingPeer.getIpv6(), inPeer.getIpv6());
+ if (inPeer.getIpv6() == null) needCheckIPv6Conflict = false;
+ else {
+ try {
+ if(inPeer.isIPv6LinkLocal())
+ needCheckIPv6Conflict = false;
+ } catch (IOException ignored) {}
+ }
+ }
+
+ return needCheckIPv6Conflict;
+ }
+}