Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration test & Simplify on-path code #114

Merged
merged 1 commit into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions doc/ReleaseHowTo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 11 additions & 21 deletions src/main/java/org/scion/jpan/internal/Segments.java
Original file line number Diff line number Diff line change
Expand Up @@ -342,31 +342,26 @@ 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();
}

// 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]);
}
Expand All @@ -380,16 +375,16 @@ 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]);
}

// hop fields
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();
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -728,6 +716,7 @@ public static SegmentType from(Seg.SegmentType segmentType) {
private static class PathSegment {
final Seg.PathSegment segment;
final List<Seg.ASEntrySignedBody> bodies;
final Seg.SegmentInformation info;
final SegmentType type; //

PathSegment(Seg.PathSegment segment, SegmentType type) {
Expand All @@ -737,6 +726,7 @@ private static class PathSegment {
segment.getAsEntriesList().stream()
.map(Segments::getBody)
.collect(Collectors.toList()));
this.info = getInfo(segment);
this.type = type;
}

Expand Down
166 changes: 166 additions & 0 deletions src/test/java/org/scion/jpan/demo/ScmpDemoDefault.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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).
*
* <p>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<Path> 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);
}
}
}
}
4 changes: 2 additions & 2 deletions src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down