diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d1ce881..dcfcec540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### TODO for 0.3.0 +- Demo that connects to Francois' website +- Support topofile port range +- `ResponsePath` is now package private (not public anymore) +- remove ScionAddress? +- Remove getPaths(long dstIsdAs, InetSocketAddress dstScionAddress) <- ISD + Scion address!!! +- ScionDatagramChannel + - GatheringByteChannel, ScatteringByteChannel + - Selector support + - Inherit DatagramChannel +- AS switching? + +### TODO post 0.3.0 +- Upgrade all JUnit topo files to post 0.11 with new format (including port range) +- ... + ### Added -- nothing yet +- Support for bootstrapper TRC metadata. [#110](https://github.com/scionproto-contrib/jpan/pull/110) ### Changed - Clean up TODO and deprecation info. [#100](https://github.com/scionproto-contrib/jpan/pull/100) diff --git a/src/main/java/org/scion/jpan/ScionService.java b/src/main/java/org/scion/jpan/ScionService.java index c7409d4cf..596b52532 100644 --- a/src/main/java/org/scion/jpan/ScionService.java +++ b/src/main/java/org/scion/jpan/ScionService.java @@ -69,7 +69,7 @@ public class ScionService { private static final String ERR_INVALID_TXT_LOG2 = ERR_INVALID_TXT + "{} {}"; private static ScionService defaultService = null; - private final LocalTopology localTopology; + private final ScionBootstrapper bootstrapper; private final DaemonServiceGrpc.DaemonServiceBlockingStub daemonStub; private final SegmentLookupServiceGrpc.SegmentLookupServiceBlockingStub segmentStub; @@ -94,22 +94,22 @@ protected ScionService(String addressOrHost, Mode mode) { channel = Grpc.newChannelBuilder(addressOrHost, InsecureChannelCredentials.create()).build(); daemonStub = DaemonServiceGrpc.newBlockingStub(channel); segmentStub = null; - localTopology = null; + bootstrapper = null; } else { LOG.info("Bootstrapping with control service: mode={} target={}", mode.name(), addressOrHost); if (mode == Mode.BOOTSTRAP_VIA_DNS) { - localTopology = ScionBootstrapper.createViaDns(addressOrHost).getTopology(); + bootstrapper = ScionBootstrapper.createViaDns(addressOrHost); } else if (mode == Mode.BOOTSTRAP_SERVER_IP) { - localTopology = ScionBootstrapper.createViaBootstrapServerIP(addressOrHost).getTopology(); + bootstrapper = ScionBootstrapper.createViaBootstrapServerIP(addressOrHost); } else if (mode == Mode.BOOTSTRAP_TOPO_FILE) { java.nio.file.Path file = Paths.get(addressOrHost); - localTopology = ScionBootstrapper.createViaTopoFile(file).getTopology(); + bootstrapper = ScionBootstrapper.createViaTopoFile(file); } else { throw new UnsupportedOperationException(); } - String csHost = localTopology.getControlServerAddress(); + String csHost = bootstrapper.getLocalTopology().getControlServerAddress(); LOG.info("Bootstrapping with control service: {}", csHost); - localIsdAs.set(localTopology.getLocalIsdAs()); + localIsdAs.set(bootstrapper.getLocalTopology().getLocalIsdAs()); // TODO InsecureChannelCredentials: Implement authentication! channel = Grpc.newChannelBuilder(csHost, InsecureChannelCredentials.create()).build(); daemonStub = null; @@ -596,7 +596,7 @@ private Long parseTxtRecordToIA(String txtEntry) { // Do not expose protobuf types on API! List getPathListCS(long srcIsdAs, long dstIsdAs) { - return Segments.getPaths(segmentStub, localTopology, srcIsdAs, dstIsdAs); + return Segments.getPaths(segmentStub, bootstrapper, srcIsdAs, dstIsdAs); } /** @@ -626,7 +626,7 @@ List getBorderRouterStrings() { .map(i -> i.getAddress().getAddress()) .collect(Collectors.toList()); } else { - return localTopology.getBorderRouterAddresses(); + return bootstrapper.getLocalTopology().getBorderRouterAddresses(); } } } diff --git a/src/main/java/org/scion/jpan/internal/GlobalTopology.java b/src/main/java/org/scion/jpan/internal/GlobalTopology.java new file mode 100644 index 000000000..5a3ea5ed6 --- /dev/null +++ b/src/main/java/org/scion/jpan/internal/GlobalTopology.java @@ -0,0 +1,187 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.scion.jpan.internal; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.time.Instant; +import java.util.*; +import org.scion.jpan.ScionRuntimeException; +import org.scion.jpan.ScionUtil; + +/** Parse a topology file into a local topology. */ +public class GlobalTopology { + + private final Map world = new HashMap<>(); + /** + * The topology is "empty" if it wasn't initialized with TRC file (or TRC metadata). THis can + * happen when it is initialized from a local topology file without bootstrap server. + */ + private final boolean isEmpty; + + public GlobalTopology(ScionBootstrapper server) { + if (server == null) { + this.isEmpty = true; + } else { + this.isEmpty = false; + getTrcFiles(server); + } + } + + public static synchronized GlobalTopology create(ScionBootstrapper server) { + return new GlobalTopology(server); + } + + public static GlobalTopology createEmpty() { + return new GlobalTopology(null); + } + + private static JsonElement safeGet(JsonObject o, String name) { + JsonElement e = o.get(name); + if (e == null) { + throw new ScionRuntimeException("Entry not found in topology file: " + name); + } + return e; + } + + public Optional isCoreAs(long isdAs) { + if (isEmpty) { + return Optional.empty(); + } + int isdCode = ScionUtil.extractIsd(isdAs); + Isd isd = world.get(isdCode); + if (isd == null) { + throw new ScionRuntimeException("Unknown ISD: " + isdCode); + } + + for (Long core : isd.coreASes) { + if (core == isdAs) { + return Optional.of(true); + } + } + return Optional.of(false); + } + + private void getTrcFiles(ScionBootstrapper server) { + String filesString = server.fetchFile("trcs"); + parseTrcFiles(filesString); + + for (Isd isd : world.values()) { + String fileName = "isd" + isd.isd + "-b" + isd.baseNumber + "-s" + isd.serialNumber; + String file = server.fetchFile("trcs/" + fileName); + parseTrcFile(file, isd); + } + } + + private void parseTrcFiles(String trcFile) { + JsonElement jsonTree = JsonParser.parseString(trcFile); + JsonArray entries = jsonTree.getAsJsonArray(); + for (int i = 0; i < entries.size(); i++) { + JsonObject entry = entries.get(i).getAsJsonObject(); + for (Map.Entry e : entry.entrySet()) { + JsonObject cs = e.getValue().getAsJsonObject(); + int base = cs.get("base_number").getAsInt(); + int isd = cs.get("isd").getAsInt(); + int serial = cs.get("serial_number").getAsInt(); + world.put(isd, new Isd(isd, base, serial)); + } + } + } + + private static void parseTrcFile(String trcFile, Isd isd) { + JsonElement jsonTree = JsonParser.parseString(trcFile); + if (jsonTree.isJsonObject()) { + JsonObject o = jsonTree.getAsJsonObject(); + + JsonArray authoritativeAses = safeGet(o, "authoritative_ases").getAsJsonArray(); + for (int i = 0; i < authoritativeAses.size(); i++) { + String isdCode = authoritativeAses.get(i).getAsString(); + isd.authorativeASes.add(ScionUtil.parseIA(isdCode)); + } + JsonArray coreAses = safeGet(o, "core_ases").getAsJsonArray(); + for (int i = 0; i < coreAses.size(); i++) { + String isdCode = coreAses.get(i).getAsString(); + isd.coreASes.add(ScionUtil.parseIA(isdCode)); + } + + isd.description = safeGet(o, "description").getAsString(); + + JsonObject id = safeGet(o, "id").getAsJsonObject(); + int base = id.get("base_number").getAsInt(); + int isdCode = id.get("isd").getAsInt(); + int serial = id.get("serial_number").getAsInt(); + if (isd.isd != isdCode || isd.baseNumber != base || isd.serialNumber != serial) { + throw new IllegalStateException("ISD/Base/Serial mismatch in TRC file."); + } + + JsonObject validity = safeGet(o, "validity").getAsJsonObject(); + String afterStr = validity.get("not_after").getAsString(); + String beforeStr = validity.get("not_before").getAsString(); + isd.notAfter = Instant.parse(afterStr); + isd.notBefore = Instant.parse(beforeStr); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Isd isd : world.values()) { + sb.append("ISD: ").append(isd).append('\n'); + } + return sb.toString(); + } + + private static class Isd { + String description; + int baseNumber; + int isd; + int serialNumber; + Instant notAfter; + Instant notBefore; + final List authorativeASes = new ArrayList<>(); + final List coreASes = new ArrayList<>(); + + Isd(int isd, int base, int serial) { + this.isd = isd; + this.baseNumber = base; + this.serialNumber = serial; + } + + @Override + public String toString() { + return "{" + + "description='" + + description + + '\'' + + ", baseNumber=" + + baseNumber + + ", isd=" + + isd + + ", serialNumber=" + + serialNumber + + ", notAfter=" + + notAfter + + ", notBefore=" + + notBefore + + ", authorativeASes=" + + authorativeASes + + ", coreASes=" + + coreASes + + '}'; + } + } +} diff --git a/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java b/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java index 4034eae1b..7ec332843 100644 --- a/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java +++ b/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java @@ -42,15 +42,18 @@ public class ScionBootstrapper { private static final Duration httpRequestTimeout = Duration.of(2, ChronoUnit.SECONDS); private final String topologyResource; private final LocalTopology topology; + private final GlobalTopology world; protected ScionBootstrapper(String topologyServiceAddress) { this.topologyResource = topologyServiceAddress; - this.topology = init(); + this.topology = initLocal(); + this.world = initGlobal(); } protected ScionBootstrapper(java.nio.file.Path file) { this.topologyResource = file.toString(); this.topology = this.init(file); + this.world = GlobalTopology.createEmpty(); } /** @@ -71,10 +74,14 @@ public static synchronized ScionBootstrapper createViaTopoFile(java.nio.file.Pat return new ScionBootstrapper(file); } - public LocalTopology getTopology() { + public LocalTopology getLocalTopology() { return topology; } + public GlobalTopology getGlobalTopology() { + return world; + } + private static String bootstrapViaDNS(String hostName) { try { String addr = DNSHelper.getScionDiscoveryAddress(hostName); @@ -87,43 +94,8 @@ private static String bootstrapViaDNS(String hostName) { } } - private static String fetchTopologyFile(URL url) throws IOException { - HttpURLConnection httpURLConnection = null; - try { - httpURLConnection = (HttpURLConnection) url.openConnection(); - httpURLConnection.setRequestMethod("GET"); - httpURLConnection.setConnectTimeout((int) httpRequestTimeout.toMillis()); - - int responseCode = httpURLConnection.getResponseCode(); - if (responseCode != HttpURLConnection.HTTP_OK) { // success - throw new ScionException( - "GET request failed (" + responseCode + ") on topology server: " + url); - } - - try (BufferedReader in = - new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) { - StringBuilder response = new StringBuilder(); - String inputLine; - while ((inputLine = in.readLine()) != null) { - response.append(inputLine).append(System.lineSeparator()); - } - return response.toString(); - } - } finally { - if (httpURLConnection != null) { - httpURLConnection.disconnect(); - } - } - } - - private LocalTopology init() { - LocalTopology topo; - try { - topo = LocalTopology.create(getTopologyFile()); - } catch (IOException e) { - throw new ScionRuntimeException( - "Error while getting topology file from " + topologyResource + ": " + e.getMessage(), e); - } + private LocalTopology initLocal() { + LocalTopology topo = LocalTopology.create(fetchFile(TOPOLOGY_ENDPOINT)); if (topo.getControlServices().isEmpty()) { throw new ScionRuntimeException( "No control servers found in topology provided by " + topologyResource); @@ -131,6 +103,10 @@ private LocalTopology init() { return topo; } + private GlobalTopology initGlobal() { + return GlobalTopology.create(this); + } + private LocalTopology init(java.nio.file.Path file) { try { if (!Files.exists(file)) { @@ -165,10 +141,43 @@ public void refreshTopology() { throw new UnsupportedOperationException(); } - private String getTopologyFile() throws IOException { - LOG.info("Getting topology from bootstrap server: {}", topologyResource); - // TODO https???? - URL url = new URL("http://" + topologyResource + "/" + BASE_URL + TOPOLOGY_ENDPOINT); - return fetchTopologyFile(url); + public String fetchFile(String resource) { + try { + LOG.info("Fetching resource from bootstrap server: {} {}", topologyResource, resource); + URL url = new URL("http://" + topologyResource + "/" + BASE_URL + resource); + return fetchFile(url); + } catch (IOException e) { + throw new ScionRuntimeException( + "While fetching resource '" + resource + "' from " + topologyResource); + } + } + + private static String fetchFile(URL url) throws IOException { + HttpURLConnection httpURLConnection = null; + try { + httpURLConnection = (HttpURLConnection) url.openConnection(); + httpURLConnection.setRequestMethod("GET"); + httpURLConnection.setConnectTimeout((int) httpRequestTimeout.toMillis()); + + int responseCode = httpURLConnection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { // success + throw new ScionException( + "GET request failed (" + responseCode + ") on topology server: " + url); + } + + try (BufferedReader in = + new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) { + StringBuilder response = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine).append(System.lineSeparator()); + } + return response.toString(); + } + } finally { + if (httpURLConnection != null) { + httpURLConnection.disconnect(); + } + } } } diff --git a/src/main/java/org/scion/jpan/internal/Segments.java b/src/main/java/org/scion/jpan/internal/Segments.java index 27675c85e..93f8e25b3 100644 --- a/src/main/java/org/scion/jpan/internal/Segments.java +++ b/src/main/java/org/scion/jpan/internal/Segments.java @@ -54,11 +54,110 @@ public class Segments { private Segments() {} + // /** + // * Lookup segments, construct paths, and return paths. + // *

+ // * Implementation notes. The sequence diagrams Gid. 43. and 4.4 in the book (edition 2022) + // suggest + // * that to always request UP, CORE and DOWN, even if paths without CORE are possible. + // * + // * @param service Segment lookup service + // * @param bootstrapper Bootstrapper + // * @param srcIsdAs source ISD/AS + // * @param dstIsdAs destination ISD/AS + // * @return list of available paths (unordered) + // */ + // public static List getPaths( + // SegmentLookupServiceGrpc.SegmentLookupServiceBlockingStub service, + // ScionBootstrapper bootstrapper, + // long srcIsdAs, + // long dstIsdAs) { + // LocalTopology localTopology = bootstrapper.getLocalTopology(); + // GlobalTopology world = bootstrapper.getGlobalTopology(); + // + // // Cases: + // // A: src==dst + // // B: srcISD==dstISD; dst==core + // // C: srcISD==dstISD; src==core + // // D: srcISD==dstISD; src==core, dst==core + // // E: srcISD==dstISD; + // // F: srcISD!=dstISD; dst==core + // // G: srcISD!=dstISD; src==core + // // H: srcISD!=dstISD; + // long srcWildcard = toWildcard(srcIsdAs); + // long dstWildcard = toWildcard(dstIsdAs); + // int srcISD = ScionUtil.extractIsd(srcIsdAs); + // int dstISD = ScionUtil.extractIsd(dstIsdAs); + // + // if (srcIsdAs == dstIsdAs) { + // // case A: same AS, return empty path + // Daemon.Path.Builder path = Daemon.Path.newBuilder(); + // path.setMtu(localTopology.getLocalMtu()); + // + // path.setExpiration(Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build()); + // return Collections.singletonList(path.build()); + // } + // + // // TODO in future we can find out whether an AS is CORE by parsing the TRC files: + // // https://docs.anapaya.net/en/latest/resources/isd-as-assignments/#as-assignments + // // ./bazel-bin/scion-pki/cmd/scion-pki/scion-pki_/scion-pki trc inspect + // // ../../Downloads/ISD64.bundle + // + // List segmentsUp = null; + // List segmentsCore = null; + // List segmentsDown = null; + // + // long from = srcIsdAs; + // long to = dstIsdAs; + // List> segments = new ArrayList<>(); + // // First, if necessary, try to get UP segments + // if (!localTopology.isLocalAsCore()) { + // // get UP segments + // // TODO find out if dstIsAs is core and directly ask for it. + // segmentsUp = getSegments(service, srcIsdAs, srcWildcard); + // boolean[] containsIsdAs = containsIsdAs(segmentsUp, srcIsdAs, dstIsdAs); + // if (containsIsdAs[1]) { + // // case B: DST is core (or on-path) + // return combineSegment(segmentsUp, localTopology); + // } + // if (segmentsUp.isEmpty()) { + // return Collections.emptyList(); + // } + // segments.add(segmentsUp); + // from = srcWildcard; + // } + // + // // TODO skip this if core has only one AS + // // Next, we look for core segments. + // // Even if the DST is reachable without a CORE segment (e.g. it is directly a reachable + // leaf) + // // we still should look at core segments because they may offer additional paths. + // // TODO: + // // Shortcutting doesn't work with core path. + // segmentsCore = getSegments(service, srcWildcard, dstWildcard); + // segments.add(segmentsCore); + // boolean[] containsIsdAs = containsIsdAs(segmentsCore, srcIsdAs, dstIsdAs); + // if (containsIsdAs[1]) { + // // dst is CORE + // return combineSegments(segments, srcIsdAs, dstIsdAs, localTopology); + // } + // + // // TODO we need to first generate all paths combinations (with 2 or 3 segments?!?!?!!!) + // // -> Store "unpacked" + // // -> Then check whether we can do shortcuts/onpath/etc + // throw new UnsupportedOperationException(); + // + // segmentsDown = getSegments(service, dstWildcard, dstIsdAs); + // segments.add(segmentsDown); + // return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, localTopology); + // } + public static List getPaths( SegmentLookupServiceGrpc.SegmentLookupServiceBlockingStub segmentStub, - LocalTopology brLookup, + ScionBootstrapper bootstrapper, long srcIsdAs, long dstIsdAs) { + LocalTopology localAS = bootstrapper.getLocalTopology(); // Cases: // A: src==dst // B: srcISD==dstISD; dst==core @@ -76,7 +175,7 @@ public static List getPaths( if (srcIsdAs == dstIsdAs) { // case A: same AS, return empty path Daemon.Path.Builder path = Daemon.Path.newBuilder(); - path.setMtu(brLookup.getLocalMtu()); + path.setMtu(localAS.getLocalMtu()); path.setExpiration(Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build()); return Collections.singletonList(path.build()); } @@ -90,14 +189,14 @@ public static List getPaths( long to = dstIsdAs; List> segments = new ArrayList<>(); // First, if necessary, try to get UP segments - if (!brLookup.isLocalAsCore()) { + if (!localAS.isLocalAsCore()) { // get UP segments // TODO find out if dstIsAs is core and directly ask for it. List segmentsUp = getSegments(segmentStub, srcIsdAs, srcWildcard); boolean[] containsIsdAs = containsIsdAs(segmentsUp, srcIsdAs, dstIsdAs); if (containsIsdAs[1]) { // case B: DST is core - return combineSegment(segmentsUp, brLookup); + return combineSegment(segmentsUp, localAS); } if (segmentsUp.isEmpty()) { return Collections.emptyList(); @@ -114,7 +213,7 @@ public static List getPaths( if (!segmentsCoreOrDown.isEmpty()) { // Okay, we found a direct route from src(Wildcard) to DST segments.add(segmentsCoreOrDown); - List paths = combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + List paths = combineSegments(segments, srcIsdAs, dstIsdAs, localAS); if (!paths.isEmpty()) { return paths; } @@ -124,7 +223,7 @@ public static List getPaths( List segmentsCore = getSegments(segmentStub, from, dstWildcard); segments.add(segmentsCore); segments.add(segmentsCoreOrDown); - return combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + return combineSegments(segments, srcIsdAs, dstIsdAs, localAS); } // Try again with wildcard (DST is neither core nor a child of FROM) List segmentsCore = getSegments(segmentStub, from, dstWildcard); @@ -133,14 +232,14 @@ public static List getPaths( if (coreHasIA[1]) { // case D: DST is core // TODO why is this ever used? See e.g. Test F0 111->120 - return combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + return combineSegments(segments, srcIsdAs, dstIsdAs, localAS); } else { from = dstWildcard; // case C: DST is not core // We have to query down segments because SRC may not have a segment connected to DST List segmentsDown = getSegments(segmentStub, from, dstIsdAs); segments.add(segmentsDown); - return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, localAS); } } // remaining cases: F, G, H @@ -151,12 +250,12 @@ public static List getPaths( boolean[] localCores = Segments.containsIsdAs(segmentsCore, 0, dstIsdAs); segments.add(segmentsCore); if (localCores[1]) { - return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, localAS); } List segmentsDown = getSegments(segmentStub, dstWildcard, dstIsdAs); segments.add(segmentsDown); - return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, brLookup); + return Segments.combineSegments(segments, srcIsdAs, dstIsdAs, localAS); } private static List getSegments( @@ -219,21 +318,21 @@ private static List getPathSegments(Seg.SegmentsResponse respon } private static List combineSegments( - List> segments, long srcIsdAs, long dstIsdAs, LocalTopology brLookup) { + List> segments, long srcIsdAs, long dstIsdAs, LocalTopology localAS) { if (segments.size() == 1) { - return combineSegment(segments.get(0), brLookup); + return combineSegment(segments.get(0), localAS); } else if (segments.size() == 2) { - return combineTwoSegments(segments.get(0), segments.get(1), srcIsdAs, dstIsdAs, brLookup); + return combineTwoSegments(segments.get(0), segments.get(1), srcIsdAs, dstIsdAs, localAS); } return combineThreeSegments( - segments.get(0), segments.get(1), segments.get(2), srcIsdAs, dstIsdAs, brLookup); + segments.get(0), segments.get(1), segments.get(2), srcIsdAs, dstIsdAs, localAS); } private static List combineSegment( - List segments, LocalTopology brLookup) { + List segments, LocalTopology localAS) { List paths = new ArrayList<>(); for (Seg.PathSegment pathSegment : segments) { - paths.add(buildPath(brLookup, pathSegment)); + paths.add(buildPath(localAS, pathSegment)); } return paths; } @@ -245,7 +344,7 @@ private static List combineSegment( * @param segments1 Core or Down segments * @param srcIsdAs src ISD/AS * @param dstIsdAs src ISD/AS - * @param brLookup border router lookup resource + * @param localAS border router lookup resource * @return Paths */ private static List combineTwoSegments( @@ -253,7 +352,7 @@ private static List combineTwoSegments( List segments1, long srcIsdAs, long dstIsdAs, - LocalTopology brLookup) { + LocalTopology localAS) { // Map IsdAs to pathSegment MultiMap segmentsMap1 = createSegmentsMap(segments1, dstIsdAs); @@ -261,7 +360,7 @@ private static List combineTwoSegments( for (Seg.PathSegment pathSegment0 : segments0) { long middleIsdAs = getOtherIsdAs(srcIsdAs, pathSegment0); for (Seg.PathSegment pathSegment1 : segmentsMap1.get(middleIsdAs)) { - paths.add(buildPath(brLookup, pathSegment0, pathSegment1)); + paths.add(buildPath(localAS, pathSegment0, pathSegment1)); } } return paths; @@ -273,7 +372,7 @@ private static List combineThreeSegments( List segmentsDown, long srcIsdAs, long dstIsdAs, - LocalTopology brLookup) { + LocalTopology localAS) { // Map IsdAs to pathSegment MultiMap upSegments = createSegmentsMap(segmentsUp, srcIsdAs); MultiMap downSegments = createSegmentsMap(segmentsDown, dstIsdAs); @@ -282,9 +381,9 @@ private static List combineThreeSegments( for (Seg.PathSegment pathSeg : segmentsCore) { long[] endIAs = getEndingIAs(pathSeg); if (upSegments.contains(endIAs[0]) && downSegments.contains(endIAs[1])) { - buildPath(paths, upSegments.get(endIAs[0]), pathSeg, downSegments.get(endIAs[1]), brLookup); + buildPath(paths, upSegments.get(endIAs[0]), pathSeg, downSegments.get(endIAs[1]), localAS); } else if (upSegments.contains(endIAs[1]) && downSegments.contains(endIAs[0])) { - buildPath(paths, upSegments.get(endIAs[1]), pathSeg, downSegments.get(endIAs[0]), brLookup); + buildPath(paths, upSegments.get(endIAs[1]), pathSeg, downSegments.get(endIAs[0]), localAS); } } return paths; @@ -295,15 +394,15 @@ private static void buildPath( List segmentsUp, Seg.PathSegment segCore, List segmentsDown, - LocalTopology brLookup) { + LocalTopology localAS) { for (Seg.PathSegment segUp : segmentsUp) { for (Seg.PathSegment segDown : segmentsDown) { - paths.add(buildPath(brLookup, segUp, segCore, segDown)); + paths.add(buildPath(localAS, segUp, segCore, segDown)); } } } - private static Daemon.Path buildPath(LocalTopology brLookup, Seg.PathSegment... segments) { + private static Daemon.Path buildPath(LocalTopology localAS, Seg.PathSegment... segments) { Daemon.Path.Builder path = Daemon.Path.newBuilder(); ByteBuffer raw = ByteBuffer.allocate(1000); @@ -322,7 +421,7 @@ private static Daemon.Path buildPath(LocalTopology brLookup, Seg.PathSegment... // info fields boolean[] reversed = new boolean[segments.length]; - long startIA = brLookup.getLocalIsdAs(); + long startIA = localAS.getLocalIsdAs(); final ByteUtil.MutLong endingIA = new ByteUtil.MutLong(-1); for (int i = 0; i < infos.length; i++) { reversed[i] = isReversed(segments[i], startIA, endingIA); @@ -331,7 +430,7 @@ private static Daemon.Path buildPath(LocalTopology brLookup, Seg.PathSegment... } // hop fields - path.setMtu(brLookup.getLocalMtu()); + path.setMtu(localAS.getLocalMtu()); for (int i = 0; i < segments.length; i++) { // bytePosSegID: 6 = 4 bytes path head + 2 byte flag in first info field writeHopFields(path, raw, 6 + i * 8, segments[i], reversed[i], infos[i]); @@ -346,7 +445,7 @@ private static Daemon.Path buildPath(LocalTopology brLookup, Seg.PathSegment... // path.setInternalHops(); // path.setNotes(); // First hop - String firstHop = brLookup.getBorderRouterAddress((int) path.getInterfaces(0).getId()); + String firstHop = localAS.getBorderRouterAddress((int) path.getInterfaces(0).getId()); Daemon.Underlay underlay = Daemon.Underlay.newBuilder().setAddress(firstHop).build(); Daemon.Interface interfaceAddr = Daemon.Interface.newBuilder().setAddress(underlay).build(); path.setInterface(interfaceAddr); diff --git a/src/test/java/org/scion/jpan/ProtobufPathDemo.java b/src/test/java/org/scion/jpan/ProtobufPathDemo.java index 9013f06d5..7122746d4 100644 --- a/src/test/java/org/scion/jpan/ProtobufPathDemo.java +++ b/src/test/java/org/scion/jpan/ProtobufPathDemo.java @@ -116,7 +116,7 @@ private void testPathsControlService(long srcIA, long dstIA) { String addr111 = "127.0.0.18:31006"; // ScionService csSercice = Scion.newServiceWithBootstrapServerIP(addr111); ScionService csSercice = - Scion.newServiceWithTopologyFile("topologies/scionproto-tiny-120.json"); + Scion.newServiceWithTopologyFile("topologies/scionproto-tiny/topology-120.json"); List paths = PackageVisibilityHelper.getPathListCS(csSercice, srcIA, dstIA); System.out.println("Paths found: " + paths.size()); for (Daemon.Path path : paths) { diff --git a/src/test/java/org/scion/jpan/ScionBootstrapperTest.java b/src/test/java/org/scion/jpan/ScionBootstrapperTest.java index 618581c47..6c3a646a7 100644 --- a/src/test/java/org/scion/jpan/ScionBootstrapperTest.java +++ b/src/test/java/org/scion/jpan/ScionBootstrapperTest.java @@ -32,9 +32,9 @@ public static void afterAll() { @Test void testTiny110() { - java.nio.file.Path topoFile = Paths.get("topologies/scionproto-tiny-110.json"); + java.nio.file.Path topoFile = Paths.get("topologies/scionproto-tiny/topology-110.json"); ScionBootstrapper sb = ScionBootstrapper.createViaTopoFile(topoFile); - LocalTopology topo = sb.getTopology(); + LocalTopology topo = sb.getLocalTopology(); assertEquals(ScionUtil.parseIA("1-ff00:0:110"), topo.getLocalIsdAs()); assertEquals("127.0.0.11:31000", topo.getControlServerAddress()); diff --git a/src/test/java/org/scion/jpan/api/ScionServiceTest.java b/src/test/java/org/scion/jpan/api/ScionServiceTest.java index e81799bac..47c077e49 100644 --- a/src/test/java/org/scion/jpan/api/ScionServiceTest.java +++ b/src/test/java/org/scion/jpan/api/ScionServiceTest.java @@ -29,10 +29,10 @@ import org.scion.jpan.*; import org.scion.jpan.internal.DNSHelper; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockDaemon; import org.scion.jpan.testutil.MockNetwork; import org.scion.jpan.testutil.MockNetwork2; -import org.scion.jpan.testutil.MockTopologyServer; import org.xbill.DNS.Lookup; import org.xbill.DNS.Name; @@ -207,7 +207,8 @@ void getPaths_localAS() throws IOException { @Test void getPaths_noPathFound_fromCore() { InetSocketAddress dstAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 12345); - try (MockNetwork2 nw = MockNetwork2.start("topologies/minimal/ASff00_0_110/topology.json")) { + try (MockNetwork2 nw = + MockNetwork2.start("topologies/minimal/", "ASff00_0_110/topology.json")) { ScionService service = Scion.defaultService(); List paths; nw.getControlServer().getAndResetCallCount(); @@ -232,7 +233,8 @@ void getPaths_noPathFound_fromCore() { @Test void getPaths_noPathFound_fromLeaf() { InetSocketAddress dstAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 12345); - try (MockNetwork2 nw = MockNetwork2.start("topologies/minimal/ASff00_0_1111/topology.json")) { + try (MockNetwork2 nw = + MockNetwork2.start("topologies/minimal/", "ASff00_0_1111/topology.json")) { ScionService service = Scion.defaultService(); List paths; nw.getControlServer().getAndResetCallCount(); @@ -341,29 +343,29 @@ void getScionAddress_Failure_BadTxtRecord() { try { Throwable t; DNSUtil.clear(); - DNSUtil.installNAPTR(MockTopologyServer.TOPO_HOST, ip, KEY_X + "yyyy=12345", KEY_X_TCP); + DNSUtil.installNAPTR(MockBootstrapServer.TOPO_HOST, ip, KEY_X + "yyyy=12345", KEY_X_TCP); t = assertThrows(ScionRuntimeException.class, Scion::defaultService); assertTrue(t.getMessage().startsWith("Could not find valid TXT ")); DNSUtil.clear(); - DNSUtil.installNAPTR(MockTopologyServer.TOPO_HOST, ip, KEY_X + "=1x2345", KEY_X_TCP); + DNSUtil.installNAPTR(MockBootstrapServer.TOPO_HOST, ip, KEY_X + "=1x2345", KEY_X_TCP); t = assertThrows(ScionRuntimeException.class, Scion::defaultService); assertTrue(t.getMessage().startsWith("Could not find valid TXT ")); DNSUtil.clear(); - DNSUtil.installNAPTR(MockTopologyServer.TOPO_HOST, ip, KEY_X + "=100000", KEY_X_TCP); + DNSUtil.installNAPTR(MockBootstrapServer.TOPO_HOST, ip, KEY_X + "=100000", KEY_X_TCP); t = assertThrows(ScionRuntimeException.class, Scion::defaultService); assertTrue(t.getMessage().startsWith("Could not find valid TXT ")); // Valid entry, but invalid port DNSUtil.clear(); - DNSUtil.installNAPTR(MockTopologyServer.TOPO_HOST, ip, KEY_X + "=10000", KEY_X_TCP); + DNSUtil.installNAPTR(MockBootstrapServer.TOPO_HOST, ip, KEY_X + "=10000", KEY_X_TCP); t = assertThrows(ScionRuntimeException.class, Scion::defaultService); - assertTrue(t.getMessage().startsWith("Error while getting topology file")); + assertTrue(t.getMessage().startsWith("While fetching resource 'topology'")); // Invalid NAPTR key DNSUtil.clear(); - DNSUtil.installNAPTR(MockTopologyServer.TOPO_HOST, ip, KEY_X + "=10000", "x-wrong:tcp"); + DNSUtil.installNAPTR(MockBootstrapServer.TOPO_HOST, ip, KEY_X + "=10000", "x-wrong:tcp"); t = assertThrows(ScionRuntimeException.class, Scion::defaultService); assertTrue(t.getMessage().startsWith("No valid DNS NAPTR entry found")); } finally { @@ -403,7 +405,8 @@ private void testInvalidTxtEntry(String txtEntry) { String host = "127.0.0.55"; System.setProperty(PackageVisibilityHelper.DEBUG_PROPERTY_DNS_MOCK, host + "=" + txtEntry); // Use any topo file - MockTopologyServer topo = MockTopologyServer.start(MockTopologyServer.TOPOFILE_TINY_110, true); + MockBootstrapServer topo = + MockBootstrapServer.start(MockBootstrapServer.TOPOFILE_TINY_110, true); try { ScionService service = Scion.defaultService(); Exception ex = @@ -524,7 +527,7 @@ void testDomainSearchResolver_nonScionHost() throws IOException { void testDomainSearchResolver() throws IOException { MockNetwork.startTiny(MockNetwork.Mode.NAPTR); try { - String searchHost = MockTopologyServer.TOPO_HOST; + String searchHost = MockBootstrapServer.TOPO_HOST; // Change to use custom search domain Lookup.setDefaultSearchPath(Name.fromString(searchHost)); // Lookup topology server diff --git a/src/test/java/org/scion/jpan/api/ScionTest.java b/src/test/java/org/scion/jpan/api/ScionTest.java index 4ac531aa6..3baf6e574 100644 --- a/src/test/java/org/scion/jpan/api/ScionTest.java +++ b/src/test/java/org/scion/jpan/api/ScionTest.java @@ -35,15 +35,15 @@ import org.junit.jupiter.api.Test; import org.scion.jpan.*; import org.scion.jpan.demo.util.ToStringUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockDaemon; import org.scion.jpan.testutil.MockNetwork; -import org.scion.jpan.testutil.MockTopologyServer; public class ScionTest { private static final String SCION_HOST = "as110.test"; private static final String SCION_TXT = "\"scion=1-ff00:0:110,127.0.0.1\""; - private static final String TOPO_FILE = "topologies/scionproto-tiny-110.json"; + private static final String TOPO_FILE = "topologies/scionproto-tiny/topology-110.json"; private static final int DEFAULT_PORT = MockDaemon.DEFAULT_PORT; @BeforeAll @@ -125,7 +125,7 @@ void defaultService_bootstrapAddress() { MockNetwork.startTiny(MockNetwork.Mode.BOOTSTRAP); try { String host; - MockTopologyServer mts = MockNetwork.getTopoServer(); + MockBootstrapServer mts = MockNetwork.getTopoServer(); if (mts.getAddress().getAddress() instanceof Inet6Address) { host = "[" + mts.getAddress().getAddress().getHostAddress() + "]"; } else { @@ -162,7 +162,7 @@ void defaultService_bootstrapNaptrRecord() { void defaultService_bootstrapTopoFile() { long dstIA = ScionUtil.parseIA("1-ff00:0:112"); InetSocketAddress dstAddress = new InetSocketAddress("::1", 12345); - MockNetwork.startTiny(MockNetwork.Mode.BOOTSTRAP); + MockNetwork.startTiny(MockNetwork.Mode.AS_ONLY); try { System.setProperty(Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, TOPO_FILE); ScionService service = Scion.defaultService(); @@ -292,7 +292,7 @@ private java.nio.file.Path getPath(String fileName) throws URISyntaxException { void newServiceWithDNS() throws IOException { long iaDst = ScionUtil.parseIA("1-ff00:0:112"); MockNetwork.startTiny(MockNetwork.Mode.NAPTR); - try (Scion.CloseableService ss = Scion.newServiceWithDNS(MockTopologyServer.TOPO_HOST)) { + try (Scion.CloseableService ss = Scion.newServiceWithDNS(MockBootstrapServer.TOPO_HOST)) { // destination address = 123.123.123.123 because we don´t care for getting a path InetAddress ip123 = InetAddress.getByAddress(new byte[] {123, 123, 123, 123}); List paths = ss.getPaths(iaDst, ip123, 12345); @@ -309,8 +309,8 @@ void newServiceWithDNS() throws IOException { @Test void newServiceWithBootstrapServer() throws IOException { long iaDst = ScionUtil.parseIA("1-ff00:0:112"); + MockNetwork.startTiny(MockNetwork.Mode.BOOTSTRAP); InetSocketAddress topoAddr = MockNetwork.getTopoServer().getAddress(); - MockNetwork.startTiny(MockNetwork.Mode.NAPTR); try (Scion.CloseableService ss = Scion.newServiceWithBootstrapServer(ToStringUtil.toAddressPort(topoAddr))) { // destination address = 123.123.123.123 because we don´t care for getting a path @@ -320,7 +320,23 @@ void newServiceWithBootstrapServer() throws IOException { assertFalse(paths.isEmpty()); assertEquals(1, MockNetwork.getTopoServer().getAndResetCallCount()); assertEquals(1, MockNetwork.getControlServer().getAndResetCallCount()); - assertNotEquals(Scion.defaultService(), ss); + // No DNS, no daemon, no ENV variables -> fail. + assertThrows(ScionRuntimeException.class, Scion::defaultService); + } finally { + MockNetwork.stopTiny(); + } + } + + @Test + void newServiceWithTopoFile() throws IOException { + long dstIA = ScionUtil.parseIA("1-ff00:0:112"); + InetSocketAddress dstAddress = new InetSocketAddress("::1", 12345); + MockNetwork.startTiny(MockNetwork.Mode.AS_ONLY); + String topofile = "topologies/scionproto-tiny/topology-110.json"; + try (Scion.CloseableService service = Scion.newServiceWithTopologyFile(topofile)) { + Path path = service.getPaths(dstIA, dstAddress).get(0); + assertNotNull(path); + assertEquals(0, MockDaemon.getAndResetCallCount()); // Daemon is not used! } finally { MockNetwork.stopTiny(); } @@ -334,7 +350,8 @@ void defaultService_bootstrapTopoFile_ScionProto_11() { MockNetwork.startTiny(MockNetwork.Mode.BOOTSTRAP); try { System.setProperty( - Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, "topologies/topology-scionproto-0.11.json"); + Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, + "topologies/scionproto-tiny/topology-scionproto-0.11.json"); ScionService service = Scion.defaultService(); Path path = service.getPaths(dstIA, dstAddress).get(0); assertNotNull(path); diff --git a/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java b/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java index 9b027629f..997f60ef9 100644 --- a/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java +++ b/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java @@ -42,6 +42,7 @@ */ public abstract class AbstractSegmentsMinimalTest { protected static final String AS_HOST = "my-as-host.org"; + protected static final String CFG_MINIMAL = "topologies/minimal/"; protected static final long ZERO = ScionUtil.parseIA("0-0:0:0"); /** ISD 1 - core AS */ diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java index e365eb431..2a83fbd22 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java @@ -30,8 +30,8 @@ import org.scion.jpan.ScionService; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockControlServer; -import org.scion.jpan.testutil.MockTopologyServer; /** * Test cases:
@@ -47,11 +47,11 @@ * I (CORE): srcISD != dstISD; (different ISDs, src/dst are cores) */ public class SegmentsMinimal110Test extends AbstractSegmentsMinimalTest { - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; @BeforeAll public static void beforeAll() { - topoServer = MockTopologyServer.start("topologies/minimal/ASff00_0_110/topology.json"); + topoServer = MockBootstrapServer.start(CFG_MINIMAL, "ASff00_0_110/topology.json"); InetSocketAddress topoAddr = topoServer.getAddress(); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); controlServer = MockControlServer.start(topoServer.getControlServerPort()); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java index a4a3e142a..ea88668ea 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java @@ -31,8 +31,8 @@ import org.scion.jpan.demo.util.ToStringUtil; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockControlServer; -import org.scion.jpan.testutil.MockTopologyServer; /** * Test cases: (with references to book p105 Fig. 5.8)
@@ -50,11 +50,11 @@ public class SegmentsMinimal1111Test extends AbstractSegmentsMinimalTest { private static String firstHop; - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; @BeforeAll public static void beforeAll() { - topoServer = MockTopologyServer.start("topologies/minimal/ASff00_0_1111/topology.json"); + topoServer = MockBootstrapServer.start(CFG_MINIMAL, "ASff00_0_1111/topology.json"); InetSocketAddress topoAddr = topoServer.getAddress(); firstHop = topoServer.getBorderRouterAddressByIA(AS_111); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java index 2da4682d0..8fbe471ce 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java @@ -29,8 +29,8 @@ import org.scion.jpan.ScionService; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockControlServer; -import org.scion.jpan.testutil.MockTopologyServer; /** * Test cases: (with references to book p105 Fig. 5.8)
@@ -48,11 +48,11 @@ public class SegmentsMinimal111Test extends AbstractSegmentsMinimalTest { private static String firstFop110; - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; @BeforeAll public static void beforeAll() { - topoServer = MockTopologyServer.start("topologies/minimal/ASff00_0_111/topology.json"); + topoServer = MockBootstrapServer.start(CFG_MINIMAL, "ASff00_0_111/topology.json"); InetSocketAddress topoAddr = topoServer.getAddress(); firstFop110 = topoServer.getBorderRouterAddressByIA(AS_110); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java index 0f6c3fc61..85c34dea4 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java @@ -30,8 +30,8 @@ import org.scion.jpan.ScionService; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockControlServer; -import org.scion.jpan.testutil.MockTopologyServer; /** * Test cases:
@@ -48,11 +48,11 @@ */ public class SegmentsMinimal120Test extends AbstractSegmentsMinimalTest { private static String firstFop210; - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; @BeforeAll public static void beforeAll() { - topoServer = MockTopologyServer.start("topologies/minimal/ASff00_0_120/topology.json"); + topoServer = MockBootstrapServer.start(CFG_MINIMAL, "ASff00_0_120/topology.json"); InetSocketAddress topoAddr = topoServer.getAddress(); firstFop210 = topoServer.getBorderRouterAddressByIA(AS_210); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java index a3ca82a13..3cedf3bc5 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java @@ -30,8 +30,8 @@ import org.scion.jpan.ScionService; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.DNSUtil; +import org.scion.jpan.testutil.MockBootstrapServer; import org.scion.jpan.testutil.MockControlServer; -import org.scion.jpan.testutil.MockTopologyServer; /** * Test cases: (with references to book p105 Fig. 5.8)
@@ -49,11 +49,11 @@ public class SegmentsMinimal121Test extends AbstractSegmentsMinimalTest { private static String firstHop120 = "127.0.0.81:31042"; - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; @BeforeAll public static void beforeAll() { - topoServer = MockTopologyServer.start("topologies/minimal/ASff00_0_121/topology.json"); + topoServer = MockBootstrapServer.start(CFG_MINIMAL, "ASff00_0_121/topology.json"); InetSocketAddress topoAddr = topoServer.getAddress(); firstHop120 = topoServer.getBorderRouterAddressByIA(AS_120); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); diff --git a/src/test/java/org/scion/jpan/testutil/AsInfo.java b/src/test/java/org/scion/jpan/testutil/AsInfo.java new file mode 100644 index 000000000..3cd51e8c6 --- /dev/null +++ b/src/test/java/org/scion/jpan/testutil/AsInfo.java @@ -0,0 +1,83 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.scion.jpan.testutil; + +import java.util.ArrayList; +import java.util.List; +import org.scion.jpan.ScionRuntimeException; +import org.scion.jpan.ScionUtil; + +public class AsInfo { + private long isdAs; + private String controlServer; + private final List borderRouters = new ArrayList<>(); + + public void setIsdAs(long isdAs) { + this.isdAs = isdAs; + } + + public void add(BorderRouter borderRouter) { + borderRouters.add(borderRouter); + } + + public void setControlServer(String addr) { + controlServer = addr; + } + + public int getControlServerPort() { + return Integer.parseInt(controlServer.substring(controlServer.indexOf(':') + 1)); + } + + public String getBorderRouterAddressByIA(long remoteIsdAs) { + for (BorderRouter br : borderRouters) { + for (BorderRouterInterface brif : br.interfaces) { + if (brif.isdAs == remoteIsdAs) { + return br.internalAddress; + } + } + } + throw new ScionRuntimeException("No router found for IsdAs " + remoteIsdAs); + } + + public long getIsdAs() { + return isdAs; + } + + public static class BorderRouter { + private final String name; + private final String internalAddress; + private final List interfaces; + + public BorderRouter(String name, String addr, List interfaces) { + this.name = name; + this.internalAddress = addr; + this.interfaces = interfaces; + } + } + + public static class BorderRouterInterface { + final int id; + final long isdAs; + final String publicUnderlay; + final String remoteUnderlay; + + public BorderRouterInterface(String id, String isdAs, String publicU, String remoteU) { + this.id = Integer.parseInt(id); + this.isdAs = ScionUtil.parseIA(isdAs); + this.publicUnderlay = publicU; + this.remoteUnderlay = remoteU; + } + } +} diff --git a/src/test/java/org/scion/jpan/testutil/JsonFileParser.java b/src/test/java/org/scion/jpan/testutil/JsonFileParser.java new file mode 100644 index 000000000..db8784b42 --- /dev/null +++ b/src/test/java/org/scion/jpan/testutil/JsonFileParser.java @@ -0,0 +1,115 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.scion.jpan.testutil; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.scion.jpan.ScionRuntimeException; +import org.scion.jpan.ScionUtil; + +public class JsonFileParser { + + public static String readFile(java.nio.file.Path file) { + file = toResourcePath(file); + StringBuilder contentBuilder = new StringBuilder(); + try (Stream stream = Files.lines(file, StandardCharsets.UTF_8)) { + stream.forEach(s -> contentBuilder.append(s).append("\n")); + } catch (IOException e) { + throw new ScionRuntimeException( + "Error reading topology file found at: " + file.toAbsolutePath()); + } + return contentBuilder.toString(); + } + + public static Path toResourcePath(Path file) { + if (file == null) { + return null; + } + try { + ClassLoader classLoader = JsonFileParser.class.getClassLoader(); + URL resource = classLoader.getResource(file.toString()); + if (resource != null) { + return Paths.get(resource.toURI()); + } + throw new IllegalArgumentException("Resource not found: " + file); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + public static Path toResourcePath(String fileName) { + if (fileName == null) { + return null; + } + return toResourcePath(Paths.get(fileName)); + } + + public static AsInfo parseTopologyFile(Path path) { + String fileStr = readFile(path); + AsInfo as = new AsInfo(); + JsonElement jsonTree = com.google.gson.JsonParser.parseString(fileStr); + if (jsonTree.isJsonObject()) { + JsonObject o = jsonTree.getAsJsonObject(); + as.setIsdAs(ScionUtil.parseIA(safeGet(o, "isd_as").getAsString())); + // localMtu = safeGet(o, "mtu").getAsInt(); + JsonObject brs = safeGet(o, "border_routers").getAsJsonObject(); + for (Map.Entry e : brs.entrySet()) { + JsonObject br = e.getValue().getAsJsonObject(); + String addr = safeGet(br, "internal_addr").getAsString(); + JsonObject ints = safeGet(br, "interfaces").getAsJsonObject(); + List interfaces = new ArrayList<>(); + for (Map.Entry ifEntry : ints.entrySet()) { + JsonObject ife = ifEntry.getValue().getAsJsonObject(); + // TODO bandwidth, mtu, ... etc + JsonObject underlay = ife.getAsJsonObject("underlay"); + interfaces.add( + new AsInfo.BorderRouterInterface( + ifEntry.getKey(), + ife.get("isd_as").getAsString(), + underlay.get("public").getAsString(), + underlay.get("remote").getAsString())); + } + as.add(new AsInfo.BorderRouter(e.getKey(), addr, interfaces)); + } + JsonObject css = safeGet(o, "control_service").getAsJsonObject(); + for (Map.Entry e : css.entrySet()) { + JsonObject cs = e.getValue().getAsJsonObject(); + as.setControlServer(cs.get("addr").getAsString()); + // controlServices.add( + // new ScionBootstrapper.ServiceNode(e.getKey(), cs.get("addr").getAsString())); + } + } + return as; + } + + private static JsonElement safeGet(JsonObject o, String name) { + JsonElement e = o.get(name); + if (e == null) { + throw new ScionRuntimeException("Entry not found in topology file: " + name); + } + return e; + } +} diff --git a/src/test/java/org/scion/jpan/testutil/MockBootstrapServer.java b/src/test/java/org/scion/jpan/testutil/MockBootstrapServer.java new file mode 100644 index 000000000..36f570355 --- /dev/null +++ b/src/test/java/org/scion/jpan/testutil/MockBootstrapServer.java @@ -0,0 +1,247 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.scion.jpan.testutil; + +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.scion.jpan.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockBootstrapServer implements Closeable { + + public static final String TOPO_HOST = "my-topo-host.org"; + public static final String CONFIG_DIR_TINY = "topologies/scionproto-tiny/"; + public static final String TOPOFILE_TINY_110 = CONFIG_DIR_TINY + "topology-110.json"; + public static final String TOPOFILE_TINY_111 = CONFIG_DIR_TINY + "topology-111.json"; + private static final Logger logger = LoggerFactory.getLogger(MockBootstrapServer.class.getName()); + private final ExecutorService executor; + private final AtomicInteger callCount = new AtomicInteger(); + private final CountDownLatch barrier = new CountDownLatch(1); + private final AtomicReference serverSocket = new AtomicReference<>(); + private final AsInfo asInfo; + + private MockBootstrapServer(Path topoFile, Path configPath, boolean installNaptr) { + getAndResetCallCount(); + Path configResource = JsonFileParser.toResourcePath(configPath); + asInfo = JsonFileParser.parseTopologyFile(topoFile); + executor = Executors.newSingleThreadExecutor(); + executor.submit(new TopologyServerImpl(JsonFileParser.readFile(topoFile), configResource)); + + try { + // Wait for sever socket address to be ready + barrier.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + if (installNaptr) { + InetSocketAddress topoAddr = serverSocket.get(); + DNSUtil.installNAPTR(TOPO_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); + System.setProperty(Constants.PROPERTY_BOOTSTRAP_NAPTR_NAME, TOPO_HOST); + } + + logger.info("Server started, listening on {}", serverSocket); + } + + public static MockBootstrapServer start() { + return new MockBootstrapServer(Paths.get(TOPOFILE_TINY_111), null, false); + } + + public static MockBootstrapServer start(String topoFile, boolean installNaptr) { + if (!TOPOFILE_TINY_110.equals(topoFile)) { + throw new UnsupportedOperationException("Add config dir"); + } + return new MockBootstrapServer(Paths.get(topoFile), Paths.get(CONFIG_DIR_TINY), installNaptr); + } + + /** + * Start a new bootstrap server. + * + * @param cfgPath path to topology folder + * @param topoFile sub-path to topology file + * @return server instance + */ + public static MockBootstrapServer start(String cfgPath, String topoFile) { + return new MockBootstrapServer(Paths.get(cfgPath + topoFile), Paths.get(cfgPath), false); + } + + @Override + public void close() { + System.clearProperty(Constants.PROPERTY_BOOTSTRAP_NAPTR_NAME); + try { + executor.shutdownNow(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + logger.error("Topology server did not terminate"); + } + logger.info("Topology server shut down"); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + DNSUtil.clear(); + } + + public InetSocketAddress getAddress() { + return serverSocket.get(); + } + + public int getControlServerPort() { + return asInfo.getControlServerPort(); + } + + public String getBorderRouterAddressByIA(long remoteIsdAs) { + return asInfo.getBorderRouterAddressByIA(remoteIsdAs); + } + + public int getAndResetCallCount() { + return callCount.getAndSet(0); + } + + private String readTrcFiles(Path resource) { + if (resource == null) { + return "[\n]"; + } + File file = new File(resource.toFile(), "trcs"); + + try { + StringWriter sw = new StringWriter(); + JsonWriter jw = new GsonBuilder().setPrettyPrinting().create().newJsonWriter(sw); + jw.beginArray(); + for (String s : file.list()) { + if (!s.endsWith(".json")) { + continue; + } + int isd = Integer.parseInt(s.substring(3, s.indexOf("-b"))); + int base = Integer.parseInt(s.substring(s.indexOf("-b") + 2, s.indexOf("-s"))); + int sn = Integer.parseInt(s.substring(s.indexOf("-s") + 2, s.indexOf("."))); + jw.beginObject().name("id").beginObject(); + jw.name("base_number").value(base); + jw.name("isd").value(isd); + jw.name("serial_number").value(sn); + jw.endObject().endObject(); + } + jw.endArray(); + return sw.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public long getLocalIsdAs() { + return asInfo.getIsdAs(); + } + + private class TopologyServerImpl implements Runnable { + private final String topologyFile; + private final String trcsFilesJson; + private final Path serverPath; + + TopologyServerImpl(String topologyFile, Path serverPath) { + this.topologyFile = topologyFile; + this.trcsFilesJson = readTrcFiles(serverPath); + this.serverPath = serverPath; + } + + @Override + public void run() { + try (ServerSocketChannel chnLocal = ServerSocketChannel.open()) { + // Explicit binding to "localhost" to avoid automatic binding to IPv6 which is not + // supported by GitHub CI (https://github.com/actions/runner-images/issues/668). + InetSocketAddress local = new InetSocketAddress(InetAddress.getLoopbackAddress(), 45678); + chnLocal.bind(local); + chnLocal.configureBlocking(true); + ByteBuffer buffer = ByteBuffer.allocate(66000); + serverSocket.set((InetSocketAddress) chnLocal.getLocalAddress()); + logger.info("Topology server started on port {}", chnLocal.getLocalAddress()); + barrier.countDown(); + while (true) { + SocketChannel ss = chnLocal.accept(); + ss.read(buffer); + SocketAddress srcAddress = ss.getRemoteAddress(); + + buffer.flip(); + + // Expected: + // "GET /topology HTTP/1.1" + // "GET /trcs HTTP/1.1" + String request = Charset.defaultCharset().decode(buffer).toString(); + String resource = request.substring(request.indexOf(" ") + 1, request.indexOf(" HTTP")); + if ("/topology".equals(resource)) { + logger.info("Bootstrap server serves file to {}", srcAddress); + callCount.incrementAndGet(); + buffer.clear(); + buffer.put(createMessage(topologyFile).getBytes()); + buffer.flip(); + ss.write(buffer); + } else if ("/trcs".equals(resource)) { + logger.info("Bootstrap server serves file to {}", srcAddress); + buffer.clear(); + buffer.put(createMessage(trcsFilesJson).getBytes()); + buffer.flip(); + ss.write(buffer); + } else if (resource.startsWith("/trcs/")) { + String fileName = resource.substring(1) + ".json"; + Path file = new File(serverPath.toFile(), fileName).toPath(); + logger.info("Bootstrap server serves file to {}: {}", srcAddress, file); + buffer.clear(); + String data = new String(Files.readAllBytes(file)); + buffer.put(createMessage(data).getBytes()); + buffer.flip(); + ss.write(buffer); + } else { + logger.warn("Illegal request: {}", request); + } + buffer.clear(); + } + + } catch (ClosedByInterruptException e) { + throw new RuntimeException(e); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new RuntimeException(e); + } finally { + logger.info("Shutting down topology server"); + } + } + + private String createMessage(String content) { + return "HTTP/1.1 200 OK\n" + + "Connection: close\n" + + "Content-Type: text/plain\n" + + "Content-Length:" + + content.length() + + "\n" + + "\n" + + content + + "\n"; + } + } +} diff --git a/src/test/java/org/scion/jpan/testutil/MockDaemon.java b/src/test/java/org/scion/jpan/testutil/MockDaemon.java index e9ed5016b..ae2d957bb 100644 --- a/src/test/java/org/scion/jpan/testutil/MockDaemon.java +++ b/src/test/java/org/scion/jpan/testutil/MockDaemon.java @@ -102,7 +102,7 @@ public MockDaemon start() throws IOException { .addService(new MockDaemon.DaemonImpl(brStr)) .build() .start(); - logger.info("Server started, listening on " + address); + logger.info("Server started, listening on {}", address); Runtime.getRuntime() .addShutdownHook( @@ -152,9 +152,8 @@ static class DaemonImpl extends DaemonServiceGrpc.DaemonServiceImplBase { @Override public void paths( Daemon.PathsRequest req, StreamObserver responseObserver) { - // logger.info( - // "Got request from client: " + req.getSourceIsdAs() + " / " + - // req.getDestinationIsdAs()); + logger.debug( + "Got request from client: {} / {}", req.getSourceIsdAs(), req.getDestinationIsdAs()); callCount.incrementAndGet(); ByteString rawPath = ByteString.copyFrom(PATH_RAW_TINY_110_112); long expirySecs = Instant.now().getEpochSecond() + Constants.DEFAULT_PATH_EXPIRY_MARGIN + 5; @@ -195,7 +194,7 @@ public void paths( @Override public void aS(Daemon.ASRequest req, StreamObserver responseObserver) { - // logger.info("Got AS request from client: " + req.getIsdAs()); + logger.debug("Got AS request from client: {}", req.getIsdAs()); callCount.incrementAndGet(); Daemon.ASResponse.Builder replyBuilder = Daemon.ASResponse.newBuilder(); if (req.getIsdAs() == 0) { // 0 -> local AS diff --git a/src/test/java/org/scion/jpan/testutil/MockNetwork.java b/src/test/java/org/scion/jpan/testutil/MockNetwork.java index 5f3515e21..e22522208 100644 --- a/src/test/java/org/scion/jpan/testutil/MockNetwork.java +++ b/src/test/java/org/scion/jpan/testutil/MockNetwork.java @@ -22,6 +22,7 @@ import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -72,7 +73,7 @@ public class MockNetwork { private static final Logger logger = LoggerFactory.getLogger(MockNetwork.class.getName()); private static ExecutorService routers = null; private static MockDaemon daemon = null; - private static MockTopologyServer topoServer; + private static MockBootstrapServer topoServer; private static MockControlServer controlServer; /** @@ -128,18 +129,24 @@ private static synchronized void startTiny(String localIP, String remoteIP, Mode brList.stream() .map(mBR -> new InetSocketAddress(BORDER_ROUTER_IPV4, mBR.getPort1())) .collect(Collectors.toList()); - try { - daemon = MockDaemon.createForBorderRouter(brAddrList).start(); - } catch (IOException e) { - throw new RuntimeException(e); + if (mode == Mode.DAEMON) { + try { + daemon = MockDaemon.createForBorderRouter(brAddrList).start(); + } catch (IOException e) { + throw new RuntimeException(e); + } } MockDNS.install(TINY_SRV_ISD_AS, TINY_SRV_NAME_1, TINY_SRV_ADDR_1); if (mode == Mode.NAPTR || mode == Mode.BOOTSTRAP) { topoServer = - MockTopologyServer.start(MockTopologyServer.TOPOFILE_TINY_110, mode == Mode.NAPTR); + MockBootstrapServer.start(MockBootstrapServer.TOPOFILE_TINY_110, mode == Mode.NAPTR); controlServer = MockControlServer.start(topoServer.getControlServerPort()); + } else if (mode == Mode.AS_ONLY) { + AsInfo asInfo = + JsonFileParser.parseTopologyFile(Paths.get(MockBootstrapServer.TOPOFILE_TINY_110)); + controlServer = MockControlServer.start(asInfo.getControlServerPort()); } dropNextPackets.getAndSet(0); @@ -226,7 +233,7 @@ public static int getForwardCount(int routerId) { return nForwards.get(routerId); } - public static MockTopologyServer getTopoServer() { + public static MockBootstrapServer getTopoServer() { return topoServer; } @@ -235,12 +242,17 @@ public static MockControlServer getControlServer() { } public enum Mode { - /** Start daemon */ + /** Start daemon (and no bootstrap server). */ DAEMON, - /** Install bootstrap server with DNS NAPTR record */ + /** Install bootstrap server with DNS NAPTR record. */ NAPTR, - /** Install bootstrap server */ - BOOTSTRAP + /** Install bootstrap server (without DNS). */ + BOOTSTRAP, + /** + * Install neither daemon nor BOOTSTRAP server (and no DNS). This is not an official scenario + * but a desirable feature. There is only a topofile, but no TRC (meta) files. + */ + AS_ONLY } } diff --git a/src/test/java/org/scion/jpan/testutil/MockNetwork2.java b/src/test/java/org/scion/jpan/testutil/MockNetwork2.java index d2801b7b7..52bcc8208 100644 --- a/src/test/java/org/scion/jpan/testutil/MockNetwork2.java +++ b/src/test/java/org/scion/jpan/testutil/MockNetwork2.java @@ -23,7 +23,7 @@ public class MockNetwork2 implements AutoCloseable { public static final String AS_HOST = "my-as-host-test.org"; // TODO remove from AbstractSegmentsMinimalTest - private final MockTopologyServer topoServer; + private final MockBootstrapServer topoServer; private final MockControlServer controlServer; static class MinimalInitializer extends AbstractSegmentsMinimalTest { @@ -36,17 +36,17 @@ void init(MockControlServer controlServer) { } } - public static MockNetwork2 start(String topoFileOfLocalAS) { - return new MockNetwork2(topoFileOfLocalAS); + public static MockNetwork2 start(String configDir, String topoFileOfLocalAS) { + return new MockNetwork2(configDir, topoFileOfLocalAS); } - private MockNetwork2(String topoFileOfLocalAS) { - topoServer = MockTopologyServer.start(topoFileOfLocalAS); + private MockNetwork2(String configDir, String topoFileOfLocalAS) { + topoServer = MockBootstrapServer.start(configDir, topoFileOfLocalAS); InetSocketAddress topoAddr = topoServer.getAddress(); DNSUtil.installNAPTR(AS_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); controlServer = MockControlServer.start(topoServer.getControlServerPort()); - System.setProperty(Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, topoFileOfLocalAS); - if (topoFileOfLocalAS.startsWith("topologies/minimal/")) { + System.setProperty(Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, configDir + topoFileOfLocalAS); + if (configDir.startsWith("topologies/minimal")) { MinimalInitializer data = new MinimalInitializer(); data.init(controlServer); data.addResponses(); @@ -70,7 +70,7 @@ public void close() { ScionService.closeDefault(); } - public MockTopologyServer getTopoServer() { + public MockBootstrapServer getTopoServer() { return topoServer; } diff --git a/src/test/java/org/scion/jpan/testutil/MockTopologyServer.java b/src/test/java/org/scion/jpan/testutil/MockTopologyServer.java deleted file mode 100644 index ea4285ef6..000000000 --- a/src/test/java/org/scion/jpan/testutil/MockTopologyServer.java +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2023 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.scion.jpan.testutil; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import java.io.*; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedByInterruptException; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Stream; -import org.scion.jpan.Constants; -import org.scion.jpan.ScionRuntimeException; -import org.scion.jpan.ScionUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MockTopologyServer implements Closeable { - - public static final String TOPO_HOST = "my-topo-host.org"; - public static final String TOPOFILE_TINY_110 = "topologies/scionproto-tiny-110.json"; - public static final String TOPOFILE_TINY_111 = "topologies/scionproto-tiny-111.json"; - private static final Logger logger = LoggerFactory.getLogger(MockTopologyServer.class.getName()); - private final ExecutorService server; - private final AtomicInteger callCount = new AtomicInteger(); - private final CountDownLatch barrier = new CountDownLatch(1); - private final AtomicReference serverSocket = new AtomicReference<>(); - private String controlServer; - private long localIsdAs; - private final List borderRouters = new ArrayList<>(); - - private MockTopologyServer(Path topoFile, boolean installNaptr) { - getAndResetCallCount(); - server = Executors.newSingleThreadExecutor(); - TopologyServerImpl serverInstance = new TopologyServerImpl(readTopologyFile(topoFile)); - server.submit(serverInstance); - - try { - // Wait for sever socket address to be ready - barrier.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - if (installNaptr) { - InetSocketAddress topoAddr = serverSocket.get(); - DNSUtil.installNAPTR(TOPO_HOST, topoAddr.getAddress().getAddress(), topoAddr.getPort()); - System.setProperty(Constants.PROPERTY_BOOTSTRAP_NAPTR_NAME, TOPO_HOST); - } - - logger.info("Server started, listening on " + serverSocket); - } - - public static MockTopologyServer start() { - return new MockTopologyServer(Paths.get(TOPOFILE_TINY_111), false); - } - - public static MockTopologyServer start(String topoFile) { - return new MockTopologyServer(Paths.get(topoFile), false); - } - - public static MockTopologyServer start(String topoFile, boolean installNaptr) { - return new MockTopologyServer(Paths.get(topoFile), installNaptr); - } - - @Override - public void close() { - System.clearProperty(Constants.PROPERTY_BOOTSTRAP_NAPTR_NAME); - try { - server.shutdownNow(); - if (!server.awaitTermination(5, TimeUnit.SECONDS)) { - logger.error("Topology server did not terminate"); - } - logger.info("Topology server shut down"); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - DNSUtil.clear(); - } - - public InetSocketAddress getAddress() { - return serverSocket.get(); - } - - public int getControlServerPort() { - return Integer.parseInt(controlServer.substring(controlServer.indexOf(':') + 1)); - } - - public String getBorderRouterAddressByIA(long remoteIsdAs) { - for (BorderRouter br : borderRouters) { - for (BorderRouterInterface brif : br.interfaces) { - if (brif.isdAs == remoteIsdAs) { - return br.internalAddress; - } - } - } - throw new ScionRuntimeException("No router found for IsdAs " + remoteIsdAs); - } - - public int getAndResetCallCount() { - return callCount.getAndSet(0); - } - - private String readTopologyFile(java.nio.file.Path file) { - try { - if (!Files.exists(file)) { - // fallback, try resource folder - ClassLoader classLoader = getClass().getClassLoader(); - URL resource = classLoader.getResource(file.toString()); - if (resource != null) { - file = Paths.get(resource.toURI()); - } - } - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - - StringBuilder contentBuilder = new StringBuilder(); - try (Stream stream = Files.lines(file, StandardCharsets.UTF_8)) { - stream.forEach(s -> contentBuilder.append(s).append("\n")); - } catch (IOException e) { - throw new ScionRuntimeException( - "Error reading topology file found at: " + file.toAbsolutePath()); - } - parseTopologyFile(contentBuilder.toString()); - return contentBuilder.toString(); - } - - private void parseTopologyFile(String topologyFile) { - JsonElement jsonTree = com.google.gson.JsonParser.parseString(topologyFile); - if (jsonTree.isJsonObject()) { - JsonObject o = jsonTree.getAsJsonObject(); - localIsdAs = ScionUtil.parseIA(safeGet(o, "isd_as").getAsString()); - // localMtu = safeGet(o, "mtu").getAsInt(); - JsonObject brs = safeGet(o, "border_routers").getAsJsonObject(); - for (Map.Entry e : brs.entrySet()) { - JsonObject br = e.getValue().getAsJsonObject(); - String addr = safeGet(br, "internal_addr").getAsString(); - JsonObject ints = safeGet(br, "interfaces").getAsJsonObject(); - List interfaces = new ArrayList<>(); - for (Map.Entry ifEntry : ints.entrySet()) { - JsonObject ife = ifEntry.getValue().getAsJsonObject(); - // TODO bandwidth, mtu, ... etc - JsonObject underlay = ife.getAsJsonObject("underlay"); - interfaces.add( - new BorderRouterInterface( - ifEntry.getKey(), - ife.get("isd_as").getAsString(), - underlay.get("public").getAsString(), - underlay.get("remote").getAsString())); - } - borderRouters.add(new BorderRouter(e.getKey(), addr, interfaces)); - } - JsonObject css = safeGet(o, "control_service").getAsJsonObject(); - for (Map.Entry e : css.entrySet()) { - JsonObject cs = e.getValue().getAsJsonObject(); - controlServer = cs.get("addr").getAsString(); - // controlServices.add( - // new ScionBootstrapper.ServiceNode(e.getKey(), cs.get("addr").getAsString())); - } - } - } - - private static JsonElement safeGet(JsonObject o, String name) { - JsonElement e = o.get(name); - if (e == null) { - throw new ScionRuntimeException("Entry not found in topology file: " + name); - } - return e; - } - - public long getLocalIsdAs() { - return localIsdAs; - } - - private class TopologyServerImpl implements Runnable { - private final String topologyFile; - - TopologyServerImpl(String topologyFile) { - this.topologyFile = topologyFile; - } - - @Override - public void run() { - try (ServerSocketChannel chnLocal = ServerSocketChannel.open()) { - // Explicit binding to "localhost" to avoid automatic binding to IPv6 which is not - // supported by GitHub CI (https://github.com/actions/runner-images/issues/668). - InetSocketAddress local = new InetSocketAddress(InetAddress.getLoopbackAddress(), 45678); - chnLocal.bind(local); - chnLocal.configureBlocking(true); - ByteBuffer buffer = ByteBuffer.allocate(66000); - serverSocket.set((InetSocketAddress) chnLocal.getLocalAddress()); - logger.info("Topology server started on port " + chnLocal.getLocalAddress()); - barrier.countDown(); - while (true) { - SocketChannel ss = chnLocal.accept(); - callCount.incrementAndGet(); - ss.read(buffer); - SocketAddress srcAddress = ss.getRemoteAddress(); - - buffer.flip(); - - String request = Charset.defaultCharset().decode(buffer).toString(); - if (request.contains("GET /topology HTTP/1.1")) { - logger.info("Topology server serves file to " + srcAddress); - buffer.clear(); - - String out = - "HTTP/1.1 200 OK\n" - + "Connection: close\n" - + "Content-Type: text/plain\n" - + "Content-Length:" - + topologyFile.length() - + "\n" - + "\n" - + topologyFile - + "\n"; - buffer.put(out.getBytes()); - buffer.flip(); - ss.write(buffer); - } else { - logger.warn("Illegal request: " + request); - } - buffer.clear(); - } - - } catch (ClosedByInterruptException e) { - throw new RuntimeException(e); - } catch (IOException e) { - logger.error(e.getMessage()); - throw new RuntimeException(e); - } finally { - logger.info("Shutting down topology server"); - } - } - } - - private static class BorderRouter { - private final String name; - private final String internalAddress; - private final List interfaces; - - public BorderRouter(String name, String addr, List interfaces) { - this.name = name; - this.internalAddress = addr; - this.interfaces = interfaces; - } - } - - private static class BorderRouterInterface { - final int id; - final long isdAs; - final String publicUnderlay; - final String remoteUnderlay; - - public BorderRouterInterface(String id, String isdAs, String publicU, String remoteU) { - this.id = Integer.parseInt(id); - this.isdAs = ScionUtil.parseIA(isdAs); - this.publicUnderlay = publicU; - this.remoteUnderlay = remoteU; - } - } -} diff --git a/src/test/resources/topologies/minimal/trcs/isd1-b1-s1.json b/src/test/resources/topologies/minimal/trcs/isd1-b1-s1.json new file mode 100644 index 000000000..bd837bcd9 --- /dev/null +++ b/src/test/resources/topologies/minimal/trcs/isd1-b1-s1.json @@ -0,0 +1,20 @@ +{ + "authoritative_ases": [ + "1-ff00:0:110", + "1-ff00:0:120" + ], + "core_ases": [ + "1-ff00:0:110", + "1-ff00:0:120" + ], + "description": "Minimal ISD 1", + "id": { + "base_number": 1, + "isd": 1, + "serial_number": 1 + }, + "validity": { + "not_after": "2124-02-20T11:45:11Z", + "not_before": "2023-02-20T11:45:11Z" + } +} diff --git a/src/test/resources/topologies/minimal/trcs/isd2-b1-s1.json b/src/test/resources/topologies/minimal/trcs/isd2-b1-s1.json new file mode 100644 index 000000000..563f851de --- /dev/null +++ b/src/test/resources/topologies/minimal/trcs/isd2-b1-s1.json @@ -0,0 +1,18 @@ +{ + "authoritative_ases": [ + "2-ff00:0:210" + ], + "core_ases": [ + "2-ff00:0:210" + ], + "description": "Minimal ISD 2", + "id": { + "base_number": 1, + "isd": 2, + "serial_number": 1 + }, + "validity": { + "not_after": "2124-02-20T11:45:11Z", + "not_before": "2023-02-20T11:45:11Z" + } +} diff --git a/src/test/resources/topologies/scionproto-tiny/README.txt b/src/test/resources/topologies/scionproto-tiny/README.txt new file mode 100644 index 000000000..cf93dd470 --- /dev/null +++ b/src/test/resources/topologies/scionproto-tiny/README.txt @@ -0,0 +1,7 @@ +This represents the scionproto tiny topology. +The files were originally copied from an older scionpropto release. + +topology-scionproto-0.11.json represent the new format as of 0.11. + +The old files wil be replaced (and support for them removed) once PRODUCTION has migrated to +0.11. \ No newline at end of file diff --git a/src/test/resources/topologies/tiny.topo b/src/test/resources/topologies/scionproto-tiny/tiny.topo similarity index 100% rename from src/test/resources/topologies/tiny.topo rename to src/test/resources/topologies/scionproto-tiny/tiny.topo diff --git a/src/test/resources/topologies/scionproto-tiny-110.json b/src/test/resources/topologies/scionproto-tiny/topology-110.json similarity index 100% rename from src/test/resources/topologies/scionproto-tiny-110.json rename to src/test/resources/topologies/scionproto-tiny/topology-110.json diff --git a/src/test/resources/topologies/scionproto-tiny-111.json b/src/test/resources/topologies/scionproto-tiny/topology-111.json similarity index 100% rename from src/test/resources/topologies/scionproto-tiny-111.json rename to src/test/resources/topologies/scionproto-tiny/topology-111.json diff --git a/src/test/resources/topologies/scionproto-tiny-112.json b/src/test/resources/topologies/scionproto-tiny/topology-112.json similarity index 100% rename from src/test/resources/topologies/scionproto-tiny-112.json rename to src/test/resources/topologies/scionproto-tiny/topology-112.json diff --git a/src/test/resources/topologies/topology-scionproto-0.11.json b/src/test/resources/topologies/scionproto-tiny/topology-scionproto-0.11.json similarity index 100% rename from src/test/resources/topologies/topology-scionproto-0.11.json rename to src/test/resources/topologies/scionproto-tiny/topology-scionproto-0.11.json diff --git a/src/test/resources/topologies/scionproto-tiny/trcs/isd1-b1-s1.json b/src/test/resources/topologies/scionproto-tiny/trcs/isd1-b1-s1.json new file mode 100644 index 000000000..6c77bcedd --- /dev/null +++ b/src/test/resources/topologies/scionproto-tiny/trcs/isd1-b1-s1.json @@ -0,0 +1,18 @@ +{ + "authoritative_ases": [ + "1-ff00:0:110" + ], + "core_ases": [ + "1-ff00:0:110" + ], + "description": "Tiny ISD 1", + "id": { + "base_number": 1, + "isd": 1, + "serial_number": 1 + }, + "validity": { + "not_after": "2124-02-20T11:45:11Z", + "not_before": "2023-02-20T11:45:11Z" + } +}