diff --git a/CHANGELOG.md b/CHANGELOG.md index 21215b71..98722774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed MTU calculations for link level MTU - Fixed some issues with IPv6 ASes - New option `EXPERIMENTAL_SCION_RESOLVER_MINIMIZE_REQUESTS` +- "Integration" test for scionproto "default". + [#116](https://github.com/scionproto-contrib/jpan/pull/116) ### Changed - Clean up TODO and deprecation info. [#100](https://github.com/scionproto-contrib/jpan/pull/100) diff --git a/doc/ReleaseHowTo.md b/doc/ReleaseHowTo.md index 05bbf69f..e53d09df 100644 --- a/doc/ReleaseHowTo.md +++ b/doc/ReleaseHowTo.md @@ -2,6 +2,12 @@ ## Release environment +0) Run integration test + - Checkout SCION proto and Start scionproto topology: + - `./scion.sh topology -c topology/default.topo` + - `./scion.sh run` + - Run `ScmpDemoDefault` in `org.scion.jpan.demo` + 1) Prepare the environment - Make sure to have a valid signing key - Make sure to have `~/.m2/settings.xml` configured properly diff --git a/src/main/java/org/scion/jpan/internal/Segments.java b/src/main/java/org/scion/jpan/internal/Segments.java index 3213f901..7335b83b 100644 --- a/src/main/java/org/scion/jpan/internal/Segments.java +++ b/src/main/java/org/scion/jpan/internal/Segments.java @@ -342,12 +342,10 @@ private static void buildPath( Daemon.Path.Builder path = Daemon.Path.newBuilder(); ByteBuffer raw = ByteBuffer.allocate(1000); - Seg.SegmentInformation[] infos = new Seg.SegmentInformation[segments.length]; int[][] ranges = new int[segments.length][]; // [start (inclusive), end (exclusive), increment] long startIA = localAS.getIsdAs(); final ByteUtil.MutLong endingIA = new ByteUtil.MutLong(-1); for (int i = 0; i < segments.length; i++) { - infos[i] = getInfo(segments[i]); ranges[i] = createRange(segments[i], startIA, endingIA); startIA = endingIA.get(); } @@ -355,18 +353,15 @@ private static void buildPath( // Search for on-path and shortcuts. if (detectOnPathUp(segments, dstIsdAs, ranges)) { segments = new PathSegment[] {segments[0]}; - infos = new Seg.SegmentInformation[] {infos[0]}; ranges = new int[][] {ranges[0]}; LOG.debug("Found on-path AS on UP segment."); } else if (detectOnPathDown(segments, localAS.getIsdAs(), ranges)) { segments = new PathSegment[] {segments[segments.length - 1]}; - infos = new Seg.SegmentInformation[] {infos[infos.length - 1]}; ranges = new int[][] {ranges[ranges.length - 1]}; LOG.debug("Found on-path AS on DOWN segment."); } else if (detectShortcut(segments, ranges)) { // The following is a no-op if there is no CORE segment segments = new PathSegment[] {segments[0], segments[segments.length - 1]}; - infos = new Seg.SegmentInformation[] {infos[0], infos[infos.length - 1]}; ranges = new int[][] {ranges[0], ranges[ranges.length - 1]}; LOG.debug("Found shortcut at hop {}:", ranges[0][1]); } @@ -380,8 +375,8 @@ private static void buildPath( raw.putInt(pathMetaHeader); // info fields - for (int i = 0; i < infos.length; i++) { - writeInfoField(raw, infos[i], ranges[i][2]); + for (int i = 0; i < segments.length; i++) { + writeInfoField(raw, segments[i].info, ranges[i][2]); calcBetaCorrection(raw, 6 + i * 8, segments[i], ranges[i]); } @@ -389,7 +384,7 @@ private static void buildPath( path.setMtu(localAS.getMtu()); 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], ranges[i], infos[i]); + writeHopFields(path, raw, 6 + i * 8, segments[i], ranges[i]); } raw.flip(); @@ -414,15 +409,9 @@ private static void calcBetaCorrection( // When we create a shortcut or on-path, we need to remove the MACs from the segID / beta. byte[] fix = new byte[2]; - // collect all macs - for (Seg.ASEntrySignedBody body : segment.bodies) { - ByteString mac = body.getHopEntry().getHopField().getMac(); - fix[0] ^= mac.byteAt(0); - fix[1] ^= mac.byteAt(1); - } - - // undo the macs we actually need: - for (int pos = range[0]; pos != range[1]; pos += range[2]) { + // We remove all MACs from start of the segment to start of the range that is actually used. + int startRange = range[2] == +1 ? range[0] : range[1] + 1; + for (int pos = 0; pos < startRange; pos++) { ByteString mac = segment.getAsEntriesList().get(pos).getHopEntry().getHopField().getMac(); fix[0] ^= mac.byteAt(0); fix[1] ^= mac.byteAt(1); @@ -487,8 +476,7 @@ private static void writeHopFields( ByteBuffer raw, int bytePosSegID, PathSegment pathSegment, - int[] range, - Seg.SegmentInformation info) { + int[] range) { int minExpiry = Integer.MAX_VALUE; for (int pos = range[0], total = 0; pos != range[1]; pos += range[2], total++) { boolean reversed = range[2] == -1; @@ -531,7 +519,7 @@ private static void writeHopFields( } // expiration - long time = calcExpTime(info.getTimestamp(), minExpiry); + long time = calcExpTime(pathSegment.info.getTimestamp(), minExpiry); if (time < path.getExpiration().getSeconds()) { path.setExpiration(Timestamp.newBuilder().setSeconds(minExpiry).build()); } @@ -667,7 +655,7 @@ private static Seg.ASEntrySignedBody getBody(Seg.ASEntry asEntry) { } } - private static Seg.SegmentInformation getInfo(PathSegment pathSegment) { + private static Seg.SegmentInformation getInfo(Seg.PathSegment pathSegment) { try { return Seg.SegmentInformation.parseFrom(pathSegment.getSegmentInfo()); } catch (InvalidProtocolBufferException e) { @@ -728,6 +716,7 @@ public static SegmentType from(Seg.SegmentType segmentType) { private static class PathSegment { final Seg.PathSegment segment; final List bodies; + final Seg.SegmentInformation info; final SegmentType type; // PathSegment(Seg.PathSegment segment, SegmentType type) { @@ -737,6 +726,7 @@ private static class PathSegment { segment.getAsEntriesList().stream() .map(Segments::getBody) .collect(Collectors.toList())); + this.info = getInfo(segment); this.type = type; } diff --git a/src/test/java/org/scion/jpan/demo/ScmpDemoDefault.java b/src/test/java/org/scion/jpan/demo/ScmpDemoDefault.java new file mode 100644 index 00000000..c6b813e4 --- /dev/null +++ b/src/test/java/org/scion/jpan/demo/ScmpDemoDefault.java @@ -0,0 +1,166 @@ +// 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.demo; + +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.util.List; +import org.scion.jpan.*; +import org.scion.jpan.internal.PathRawParser; +import org.scion.jpan.testutil.Scenario; + +/** + * This demo tests various scenarios on the scionproto "default" topology. + * + *

The current implementation only validates the number of hops (i.e. that a pth is indeed + * "short") and that the path works (i.e. is accepted by the scionproto mock network). + * + *

Each test is run twice: once with getting the path from the control server and once with + * getting the path from the daemon. + */ +public class ScmpDemoDefault { + + public static boolean PRINT = true; + private static int REPEAT = 2; + // Use a port from the dispatcher compatibility range + private static final int LOCAL_PORT = 32766; + private static final String PREFIX = "topologies/scionproto-default/"; + + public static void init(boolean print, int repeat) { + PRINT = print; + REPEAT = repeat; + } + + public static void main(String[] args) throws IOException { + // Peering direct + // run("1-ff00:0:133", "1-ff00:0:122", "ASff00_0_133/topology.json", 1); + // run("1-ff00:0:133", "1-ff00:0:122", null, 1); + + // Peering via parent + // run("1-ff00:0:132", "1-ff00:0:122", "ASff00_0_132/topology.json", 5); + // run("1-ff00:0:132", "1-ff00:0:122", null, 5); + + // On path down, IPv4 + run("1-ff00:0:131", "1-ff00:0:132", "ASff00_0_131/topology.json", 2); + run("1-ff00:0:131", "1-ff00:0:132", null, 2); + + // On path down, IPv6 + run("1-ff00:0:132", "1-ff00:0:133", "ASff00_0_132/topology.json", 2); + run("1-ff00:0:132", "1-ff00:0:133", null, 2); + + // On path up, IPv4 + run("1-ff00:0:133", "1-ff00:0:131", "ASff00_0_133/topology.json", 3); + run("1-ff00:0:133", "1-ff00:0:131", null, 3); + + // On path up, IPv6 + run("1-ff00:0:132", "1-ff00:0:131", "ASff00_0_132/topology.json", 2); + run("1-ff00:0:132", "1-ff00:0:131", null, 2); + + // Shortcut, IPv4 + run("2-ff00:0:212", "2-ff00:0:222", "ASff00_0_212/topology.json", 4); + run("2-ff00:0:212", "2-ff00:0:222", null, 4); + + // Shortcut, IPv6 + run("2-ff00:0:222", "2-ff00:0:212", "ASff00_0_222/topology.json", 4); + run("2-ff00:0:222", "2-ff00:0:212", null, 4); + } + + public static void run(String src, String dst, String topoFile, int nHops) throws IOException { + // Same as: + // scion ping 2-ff00:0:211,127.0.0.10 --sciond 127.0.0.43:30255 + try { + // Use scenario builder to get access to relevant IP addresses + Scenario scenario = Scenario.readFrom(PREFIX); + long srcIsdAs = ScionUtil.parseIA(src); + long dstIsdAs = ScionUtil.parseIA(dst); + + if (topoFile != null) { + // Alternative #1: Bootstrap from topo file + System.setProperty(Constants.PROPERTY_BOOTSTRAP_TOPO_FILE, PREFIX + topoFile); + } else { + // Alternative #2: Bootstrap from SCION daemon + System.setProperty(Constants.PROPERTY_DAEMON, scenario.getDaemon(srcIsdAs)); + } + + // Ping the dispatcher/shim. It listens on the same IP as the control service. + InetAddress ip = scenario.getControlServer(dstIsdAs).getAddress(); + + // Get paths + List paths = Scion.defaultService().getPaths(dstIsdAs, ip, Constants.SCMP_PORT); + Path path = PathPolicy.MIN_HOPS.filter(paths); + runDemo(path); + new HopValidator(nHops).validate(path); + } finally { + Scion.closeDefault(); + } + } + + private static void runDemo(Path path) throws IOException { + ByteBuffer data = ByteBuffer.allocate(0); + printPath(path); + try (ScmpChannel scmpChannel = Scmp.createChannel(LOCAL_PORT)) { + for (int i = 0; i < REPEAT; i++) { + Scmp.EchoMessage msg = scmpChannel.sendEchoRequest(path, i, data); + String millis = String.format("%.3f", msg.getNanoSeconds() / (double) 1_000_000); + String echoMsgStr = msg.getSizeReceived() + " bytes from "; + InetAddress addr = msg.getPath().getRemoteAddress(); + echoMsgStr += ScionUtil.toStringIA(path.getRemoteIsdAs()) + "," + addr.getHostAddress(); + echoMsgStr += ": scmp_seq=" + msg.getSequenceNumber(); + if (msg.isTimedOut()) { + echoMsgStr += " Timed out after"; + } + echoMsgStr += " time=" + millis + "ms"; + println(echoMsgStr); + try { + if (i < REPEAT - 1) { + Thread.sleep(1000); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + + private static void printPath(Path path) { + String sb = "Hops: " + ScionUtil.toStringPath(path.getMetadata()); + sb += " MTU: " + path.getMetadata().getMtu(); + sb += " NextHop: " + path.getMetadata().getInterface().getAddress(); + println(sb); + } + + private static void println(String msg) { + if (PRINT) { + System.out.println(msg); + } + } + + private static class HopValidator { + final int nHops; + + private HopValidator(int nHops) { + this.nHops = nHops; + } + + void validate(Path path) { + PathRawParser ph = PathRawParser.create(path.getRawPath()); + int hopCount = ph.getSegLen(0) + ph.getSegLen(1) + ph.getSegLen(2); + if (hopCount != nHops) { + throw new IllegalStateException("Expected: " + nHops + " but got " + hopCount); + } + } + } +} diff --git a/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java b/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java index 352977f6..66eb654d 100644 --- a/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java +++ b/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java @@ -55,11 +55,11 @@ public static void init(boolean print, Network network, int repeat) { REPEAT = repeat; } - public ScmpEchoDemo() { + private ScmpEchoDemo() { this(12345); } - public ScmpEchoDemo(int localPort) { + private ScmpEchoDemo(int localPort) { this.localPort = localPort; }