Skip to content
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 @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- Support for --log.level
[#4](https://github.com/netsec-ethz/scion-java-multiping/pull/4)
- Added path probing for showpaths and --no-probe
[#5](https://github.com/netsec-ethz/scion-java-multiping/pull/5)

### Fixed

Expand Down
26 changes: 12 additions & 14 deletions src/main/java/org/scion/cli/Cli.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static void main(String... args) {
System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "ERROR");
handleExit(() -> run(args));
// handleExit(() -> run(new String[] {"traceroute", "66-2:0:18,10.0.0.1"}));
// handleExit(() -> run(new String[] {"showpaths", "67-401500"}));
}

public static void run(String... args) throws IOException {
Expand Down Expand Up @@ -207,7 +208,7 @@ static void printUsagePing() {
println(" --sciond string SCION Daemon address. (default \"127.0.0.1:30255\")");
// println(" --sequence string Space separated list of hop predicates");
println(" --shim start with SHIM enabled (default disabled).");
println(" --timeout duration timeout per packet (default 1s)");
println(" --timeout uint16 timeout per packet in milliseconds (default 1s)");
println(" --url url use and resolve a url as destination address");
}

Expand Down Expand Up @@ -245,24 +246,21 @@ static void printUsageTraceroute() {
println(" --sciond string SCION Daemon address. (default \"127.0.0.1:30255\")");
// println(" --sequence string Space separated list of hop predicates");
println(" --shim start with SHIM enabled (default disabled).");
println(" --timeout duration timeout per packet (default 1s)");
println(" --timeout uint16 timeout per packet in milliseconds (default 1s)");
println(" --url url use and resolve a url as destination address");
}

static void printUsageShowpaths() {
println("'showpaths' lists available paths between the local and the specified");
println("SCION AS.");
println("");
println("Path probing: Unlike `scion`, jpan-cli does no path probing."); // TODO
// println("By default, the paths are probed. Paths served from the SCION Daemon's might
// not");
// println("forward traffic successfully (e.g. if a network link went down, or there is a
// black");
// println("hole on the path). To disable path probing, set the appropriate flag.");
println("By default, the paths are probed. Paths served from the SCION Daemon's might not");
println("forward traffic successfully (e.g. if a network link went down, or there is a black");
println("hole on the path). To disable path probing, set the appropriate flag.");
println("");
// println("If no alive path is discovered, json output is not enabled, and probing is not");
// println("disabled, showpaths will exit with the code 1.");
println("If no alive path is discovered, showpaths will exit with the code 1."); // TODO
// println("If no alive path is discovered, json output is not enabled, and probing is not");
println("If no alive path is discovered, and probing is not");
println("disabled, showpaths will exit with the code 1.");
println("On other errors, showpaths will exit with code 2.");
// println("");
// printSequenceHelp();
Expand Down Expand Up @@ -299,12 +297,12 @@ static void printUsageShowpaths() {
println(
" -m, --maxpaths int maximum number of paths that are displayed (default 10)");
// println(" --no-color disable colored output");
// println(" --no-probe Do not probe the paths and print the health
// status");
println(" --no-probe Do not probe the paths and print the health status");
println(" --port uint16 Use specified local port for probing");
// println(" -r, --refresh Set refresh flag for SCION Daemon path request");
println(" --sciond string SCION Daemon address. (default \"127.0.0.1:30255\")");
// println(" --sequence string Space separated list of hop predicates");
// println(" --timeout duration Timeout (default 5s)");
println(" --timeout uint16 Probing timeout in milliseconds (default 5s)");
// println(" --tracing.agent string Tracing agent address");
}

Expand Down
37 changes: 27 additions & 10 deletions src/main/java/org/scion/cli/Showpaths.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import org.scion.cli.util.Errors;
import org.scion.cli.util.ExitCodeException;
import org.scion.cli.util.Prober;
import org.scion.jpan.*;

/**
Expand All @@ -50,6 +49,9 @@ public class Showpaths {
private Long isdAs;
private boolean extended = false;
private int maxPaths = 10;
private Integer port;
private final int timeoutMs = 5000;
private boolean probePath = true;

public static void main(String... args) {
handleExit(() -> new Showpaths().run(args));
Expand Down Expand Up @@ -102,6 +104,9 @@ private void parseArgs(String[] argsArray) {
case "--maxpaths":
maxPaths = parseInt("--maxpaths", args);
break;
case "--no-probe":
probePath = false;
break;
case "--sciond":
daemon = parseAddress("sciond", args);
break;
Expand All @@ -127,6 +132,13 @@ public void run() throws IOException {
throw new ExitCodeException(1, "No path found from " + src + " to " + dst);
}

Map<Integer, Prober.Status> isActive;
if (probePath) {
isActive = Prober.probe(port, timeoutMs, paths);
} else {
isActive = new HashMap<>();
}

println("Available paths to " + ScionUtil.toStringIA(isdAs));

int id = 0;
Expand All @@ -143,23 +155,29 @@ public void run() throws IOException {
String header = "[" + id++ + "] Hops: " + ScionUtil.toStringPath(meta);
if (extended) {
println(header);
printExtended(path, localIP);
printExtended(path, localIP, isActive.getOrDefault(id, Prober.Status.Unknown));
} else {
String compact =
" MTU: "
+ meta.getMtu()
+ " NextHop: "
+ path.getFirstHopAddress().getHostString()
+ ":"
+ path.getFirstHopAddress().getPort()
+ " LocalIP: "
+ localIP;
+ path.getFirstHopAddress().getPort();
if (probePath) {
compact += " Status: " + isActive.getOrDefault(id, Prober.Status.Unknown);
}
compact += " LocalIP: " + localIP;
println(header + compact);
}
}

if (probePath && isActive.isEmpty()) {
throw new ExitCodeException(2, "Error: no path alive");
}
}

private static void printExtended(Path path, String localIP) {
private static void printExtended(Path path, String localIP, Prober.Status status) {
StringBuilder sb = new StringBuilder();
String NL = System.lineSeparator();

Expand All @@ -173,8 +191,7 @@ private static void printExtended(Path path, String localIP) {
sb.append(" LinkType: ").append(toStringLinkType(meta)).append(NL);
sb.append(" Notes: ").append(toStringNotes(meta)).append(NL);
sb.append(" SupportsEPIC: ").append(toStringEPIC(meta)).append(NL);
// TODO, see private/app/path/pathprobe/paths.go
sb.append(" Status: ").append("unknown").append(NL);
sb.append(" Status: ").append(status).append(NL);
// TODO use destination IP from returned packet from probe
sb.append(" LocalIP: ").append(localIP).append(NL);

Expand Down
114 changes: 114 additions & 0 deletions src/main/java/org/scion/cli/util/Prober.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2026 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.cli.util;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.scion.jpan.*;

public class Prober {

public enum Status {
// StatusUnknown indicates that it is not clear what state the path is in.
Unknown,
// StatusTimeout indicates that a reply did come back in time for the path.
Timeout,
// StatusAlive indicates that the expected reply did come back in time.
Alive,
// StatusSCMP indicates that an unexpected SCMP packet came in the reply.
SCMP;
}

private Prober() {}

public static Map<Integer, Status> probe(Integer port, int timeoutMs, List<Path> paths) {
PingResponseHandler handler = new PingResponseHandler(paths.size());

// Send all requests
ScmpSenderAsync.Builder builder = Scmp.newSenderAsyncBuilder(handler);
if (port != null) {
builder.setLocalPort(port);
}
try (ScmpSenderAsync sender = builder.build()) {
sender.setTimeOut(timeoutMs);
for (Path path : paths) {
sender.sendTracerouteLast(path);
}

// Wait for all messages to be received, BEFORE closing the "sender".
handler.await();
} catch (IOException e) {
throw new ExitCodeException(2, e.getMessage());
}

return handler.result;
}

private static class PingResponseHandler implements ScmpSenderAsync.ResponseHandler {
private final Map<Integer, Status> result = new ConcurrentHashMap<>();
private final CountDownLatch barrier;
private final AtomicInteger errors = new AtomicInteger();
private final int nPaths;

private PingResponseHandler(int nPaths) {
this.nPaths = nPaths;
barrier = new CountDownLatch(nPaths);
}

@Override
public void onResponse(Scmp.TimedMessage msg) {
barrier.countDown();
result.put(msg.getSequenceNumber(), Status.Alive);
}

@Override
public void onTimeout(Scmp.TimedMessage msg) {
barrier.countDown();
result.put(msg.getSequenceNumber(), Status.Timeout);
}

@Override
public void onError(Scmp.ErrorMessage msg) {
errors.incrementAndGet();
barrier.countDown();
result.put(msg.getSequenceNumber(), Status.SCMP);
}

@Override
public void onException(Throwable t) {
errors.incrementAndGet();
barrier.countDown();
}

void await() {
try {
if (!barrier.await(1100, TimeUnit.MILLISECONDS)) {
throw new IllegalStateException("Missing messages: " + barrier.getCount() + "/" + nPaths);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
}

public boolean hasErrors() {
return errors.get() > 0;
}
}
}
1 change: 1 addition & 0 deletions src/main/java/org/scion/cli/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public static void handleExit(Runner runner) {
}
System.exit(e.exitCode());
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
Expand Down
Loading