From ac306c5a55609957f9cc8c0638fe2a1f94ef0bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= Date: Mon, 16 Mar 2026 16:27:06 +0100 Subject: [PATCH 1/3] Logging level and other stuff --- CHANGELOG.md | 5 +- src/main/java/org/scion/cli/Address.java | 18 ++++--- src/main/java/org/scion/cli/Cli.java | 42 +++++++++------- src/main/java/org/scion/cli/Ping.java | 50 ++++++++++--------- .../java/org/scion/cli/PingResponder.java | 22 ++++---- src/main/java/org/scion/cli/Showpaths.java | 46 +++++++++-------- src/main/java/org/scion/cli/Traceroute.java | 45 +++++++++-------- src/main/java/org/scion/cli/util/Errors.java | 21 ++++++++ src/main/java/org/scion/cli/util/Util.java | 25 ++++++++-- src/test/java/org/scion/cli/AddressTest.java | 40 +++++++++++++++ src/test/java/org/scion/cli/CliTest.java | 39 +++++++++++++++ .../java/org/scion/cli/PingResponderTest.java | 40 +++++++++++++++ src/test/java/org/scion/cli/PingTest.java | 50 +++++++++++++++++++ .../java/org/scion/cli/ShowpathsTest.java | 49 ++++++++++++++++++ .../java/org/scion/cli/TracerouteTest.java | 50 +++++++++++++++++++ 15 files changed, 438 insertions(+), 104 deletions(-) create mode 100644 src/main/java/org/scion/cli/util/Errors.java create mode 100644 src/test/java/org/scion/cli/AddressTest.java create mode 100644 src/test/java/org/scion/cli/CliTest.java create mode 100644 src/test/java/org/scion/cli/PingResponderTest.java create mode 100644 src/test/java/org/scion/cli/PingTest.java create mode 100644 src/test/java/org/scion/cli/ShowpathsTest.java create mode 100644 src/test/java/org/scion/cli/TracerouteTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ece7d10..9742a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. -- Nothing yet +### Added +- Support for --log.level + [#4](https://github.com/netsec-ethz/scion-java-multiping/pull/4) -### Changed +### Fixed - Fixed showpaths issues. [#2](https://github.com/netsec-ethz/scion-java-multiping/pull/2) diff --git a/src/main/java/org/scion/cli/Address.java b/src/main/java/org/scion/cli/Address.java index 01e7146..366442a 100644 --- a/src/main/java/org/scion/cli/Address.java +++ b/src/main/java/org/scion/cli/Address.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import org.scion.cli.util.Errors; import org.scion.cli.util.ExitCodeException; import org.scion.jpan.*; import org.scion.jpan.internal.IPHelper; @@ -41,15 +42,15 @@ */ public class Address { - private static long localIsdAs = 0; - private static InetAddress localIP = null; - private static InetSocketAddress daemon; + private long localIsdAs = 0; + private InetAddress localIP = null; + private InetSocketAddress daemon; public static void main(String... args) { - handleExit(() -> run(args)); + handleExit(() -> new Address().run(args)); } - public static void run(String... args) throws IOException { + public void run(String... args) throws IOException { parseArgs(args); if (daemon != null) { System.setProperty(Constants.PROPERTY_DAEMON, daemon.toString()); @@ -61,7 +62,7 @@ public static void run(String... args) throws IOException { } } - private static void parseArgs(String[] argsArray) { + private void parseArgs(String[] argsArray) { List args = new ArrayList<>(Arrays.asList(argsArray)); while (!args.isEmpty()) { switch (args.get(0)) { @@ -77,11 +78,14 @@ private static void parseArgs(String[] argsArray) { case "--local": localIP = parseIP("local", args); break; + case "--log.level": + parseAndSetLogLevel(args); + break; case "--sciond": daemon = parseAddress("sciond", args); break; default: - throw new ExitCodeException(2, "Unknown option: " + args.get(0)); + throw new ExitCodeException(2, Errors.UNKNOWN_OPTION + args.get(0)); } args.remove(0); } diff --git a/src/main/java/org/scion/cli/Cli.java b/src/main/java/org/scion/cli/Cli.java index 9312902..94b4e3b 100644 --- a/src/main/java/org/scion/cli/Cli.java +++ b/src/main/java/org/scion/cli/Cli.java @@ -19,44 +19,47 @@ import static org.scion.cli.util.Util.*; +import java.io.IOException; import java.util.Arrays; import java.util.Locale; +import org.scion.cli.util.ExitCodeException; public class Cli { private static final String VERSION = "0.1.0 (using JPAN 0.6.1)"; - public static void main(String[] args) { + 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"})); } - public static void run(String[] args) { + public static void run(String... args) throws IOException { checkArgs(args, 1, Integer.MAX_VALUE); String mode = args[0].toLowerCase(Locale.ROOT); String[] newArgs = Arrays.copyOfRange(args, 1, args.length); switch (mode) { case "address": - Address.main(newArgs); + new Address().run(newArgs); return; case "h": case "help": printHelp(args.length == 1 ? "" : args[1]); return; case "ping": - Ping.main(newArgs); + new Ping().run(newArgs); return; case "pr": case "ping-responder": - PingResponder.main(newArgs); + new PingResponder().run(newArgs); return; case "sp": case "showpaths": - Showpaths.main(newArgs); + new Showpaths().run(newArgs); return; case "tr": case "traceroute": - Traceroute.main(newArgs); + new Traceroute().run(newArgs); return; case "v": case "version": @@ -64,7 +67,7 @@ public static void run(String[] args) { return; default: printUsage(); - System.exit(1); + throw new ExitCodeException(2, "Unknown command: \"" + mode + "\""); } } @@ -72,7 +75,7 @@ private static void checkArgs(String[] args, int minArgs, int maxArgs) { if (args.length < minArgs || args.length > maxArgs) { println("Invalid number of arguments."); printUsage(); - System.exit(1); + throw new ExitCodeException(2, "Invalid number of arguments."); } } @@ -138,11 +141,13 @@ private static void printUsageAddress() { println(" jpan-cli address"); println(""); println("Flags:"); - println(" -h, --help help for address"); + println(" -h, --help help for address"); // println(" --isd-as isd-as The local ISD-AS to use. (default 0-0)"); // println(" --json Write the output as machine readable json"); - println(" -l, --local ip Local IP address to listen on. (default invalid IP)"); - println(" --sciond string SCION Daemon address. (default \"127.0.0.1:30255\")"); + println(" -l, --local ip Local IP address to listen on. (default invalid IP)"); + println( + " --log.level string Console logging level verbosity (debug|info|warn|error)"); + println(" --sciond string SCION Daemon address. (default \"127.0.0.1:30255\")"); } static void printUsagePing() { @@ -180,7 +185,8 @@ static void printUsagePing() { println(" --interval duration time between packets (default 1s)"); // println(" --isd-as isd-as The local ISD-AS to use. (default 0-0)"); println(" -l, --local ip Local IP address to listen on. (default invalid IP)"); - // println(" --log.level string Console logging level verbosity (debug|info|error)"); + println( + " --log.level string Console logging level verbosity (debug|info|warn|error)"); // println(" --max-mtu choose the payload size such that the sent SCION // packet including the SCION Header,"); // println(" SCMP echo header and payload are equal to the MTU @@ -231,8 +237,8 @@ static void printUsageTraceroute() { // println(" --isd-as isd-as The local ISD-AS to use. (default 0-0)"); // println(" -l, --local ip Local IP address to listen on. (default invalid // IP)"); - // println(" --log.level string Console logging level verbosity - // (debug|info|error)"); + println( + " --log.level string Console logging level verbosity (debug|info|warn|error)"); // println(" --no-color disable colored output"); println(" --port uint16 use specified local port"); // println(" --refresh set refresh flag for path request"); @@ -288,8 +294,8 @@ static void printUsageShowpaths() { println(" -h, --help help for showpaths"); // println(" --isd-as isd-as The local ISD-AS to use. (default 0-0)"); println(" -l, --local ip Local IP address to listen on. (default invalid IP)"); - // println(" --log.level string Console logging level verbosity - // (debug|info|error)"); + println( + " --log.level string Console logging level verbosity (debug|info|warn|error)"); println( " -m, --maxpaths int maximum number of paths that are displayed (default 10)"); // println(" --no-color disable colored output"); @@ -320,6 +326,8 @@ static void printUsagePingResponder() { println("Flags:"); // println(" -l, --local ip Local IP address to listen on. (default invalid // IP)"); + println( + " --log.level string Console logging level verbosity (debug|info|warn|error)"); println(" --port uint16 use specified local port."); } diff --git a/src/main/java/org/scion/cli/Ping.java b/src/main/java/org/scion/cli/Ping.java index 16a644c..93b9032 100644 --- a/src/main/java/org/scion/cli/Ping.java +++ b/src/main/java/org/scion/cli/Ping.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.scion.cli.util.Errors; import org.scion.cli.util.ExitCodeException; import org.scion.cli.util.Util; import org.scion.jpan.*; @@ -33,23 +34,23 @@ */ public class Ping { - private static Integer localPort = -1; - private static int count = 10; - private static int intervalMs = 1000; - private static boolean startShim = false; - private static long localIsdAs = 0; - private static InetAddress localIP = null; - private static int payloadSize = 0; - private static ScionAddress dstAddress; - private static String dstUrl; - private static InetSocketAddress daemon; - private static int timeoutMs = 1000; + private Integer localPort = -1; + private int count = 10; + private int intervalMs = 1000; + private boolean startShim = false; + private long localIsdAs = 0; + private InetAddress localIP = null; + private int payloadSize = 0; + private ScionAddress dstAddress; + private String dstUrl; + private InetSocketAddress daemon; + private int timeoutMs = 1000; public static void main(String... args) { - handleExit(() -> run(args)); + handleExit(() -> new Ping().run(args)); } - public static void run(String... args) throws IOException { + public void run(String... args) throws IOException { parseArgs(args); System.setProperty(Constants.PROPERTY_SHIM, startShim ? "true" : "false"); // disable SHIM if (daemon != null) { @@ -71,9 +72,16 @@ public static void run(String... args) throws IOException { } } - private static void parseArgs(String[] argsArray) { + private void parseArgs(String[] argsArray) { List args = new ArrayList<>(Arrays.asList(argsArray)); while (!args.isEmpty()) { + if (!args.get(0).startsWith("-")) { + if (dstAddress == null) { + dstAddress = parseScionAddress(args); + continue; + } + throw new ExitCodeException(2, Errors.UNEXPECTED_NON_FLAG + args.get(0)); + } switch (args.get(0)) { case "-c": case "--count": @@ -94,6 +102,9 @@ private static void parseArgs(String[] argsArray) { case "--local": localIP = parseIP("local", args); break; + case "--log.level": + parseAndSetLogLevel(args); + break; case "--port": localPort = parseInt("port", args); break; @@ -114,20 +125,13 @@ private static void parseArgs(String[] argsArray) { dstUrl = parseString("url", args); break; default: - if (dstAddress == null) { - dstAddress = parseScionAddress(args); - if (dstAddress != null) { - args.remove(0); - continue; - } - } - throw new ExitCodeException(2, "Unknown option: " + args.get(0)); + throw new ExitCodeException(2, Errors.UNKNOWN_OPTION + args.get(0)); } args.remove(0); } } - private static void run(List paths) throws IOException { + private void run(List paths) throws IOException { Path path = paths.get(0); ByteBuffer data = ByteBuffer.allocate(payloadSize); for (int i = 0; i < payloadSize; i++) { diff --git a/src/main/java/org/scion/cli/PingResponder.java b/src/main/java/org/scion/cli/PingResponder.java index 3301819..f5ce94b 100644 --- a/src/main/java/org/scion/cli/PingResponder.java +++ b/src/main/java/org/scion/cli/PingResponder.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.scion.cli.util.Errors; import org.scion.cli.util.ExitCodeException; import org.scion.cli.util.Util; import org.scion.jpan.*; @@ -28,14 +29,14 @@ /** A simple echo responder that responds to SCMP echo requests. */ public class PingResponder { - private static int localPort = 30041; - private static InetAddress localIP = null; + private int localPort = 30041; + private InetAddress localIP = null; public static void main(String[] args) { - handleExit(() -> run(args)); + handleExit(() -> new PingResponder().run(args)); } - public static void run(String[] args) throws IOException { + public void run(String[] args) throws IOException { parseArgs(args); if (localPort == 30041) { System.setProperty(Constants.PROPERTY_SHIM, "false"); // disable SHIM @@ -51,25 +52,26 @@ public static void run(String[] args) throws IOException { } } - private static void parseArgs(String[] argsArray) { + private void parseArgs(String[] argsArray) { List args = new ArrayList<>(Arrays.asList(argsArray)); while (!args.isEmpty()) { switch (args.get(0)) { - case "h": + case "-h": case "--help": Cli.printUsagePingResponder(); throw new ExitCodeException(0); - case "l": + case "-l": case "--local": localIP = parseIP("local", args); break; + case "--log.level": + parseAndSetLogLevel(args); + break; case "--port": localPort = parseInt("port", args); break; default: - Util.println("Unknown option: " + args.get(0)); - Cli.printUsagePing(); - System.exit(1); + throw new ExitCodeException(2, Errors.UNKNOWN_OPTION + args.get(0)); } args.remove(0); } diff --git a/src/main/java/org/scion/cli/Showpaths.java b/src/main/java/org/scion/cli/Showpaths.java index ff3b843..8ea805f 100644 --- a/src/main/java/org/scion/cli/Showpaths.java +++ b/src/main/java/org/scion/cli/Showpaths.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.scion.cli.util.Errors; import org.scion.cli.util.ExitCodeException; import org.scion.jpan.*; @@ -43,18 +44,18 @@ */ public class Showpaths { - private static long localIsdAs = 0; - private static InetAddress localIP = null; - private static InetSocketAddress daemon; - private static Long isdAs; - private static boolean extended = false; - private static int maxPaths = 10; + private long localIsdAs = 0; + private InetAddress localIP = null; + private InetSocketAddress daemon; + private Long isdAs; + private boolean extended = false; + private int maxPaths = 10; public static void main(String... args) { - handleExit(() -> run(args)); + handleExit(() -> new Showpaths().run(args)); } - public static void run(String... args) throws IOException { + public void run(String... args) throws IOException { parseArgs(args); if (daemon != null) { System.setProperty(Constants.PROPERTY_DAEMON, daemon.toString()); @@ -66,9 +67,17 @@ public static void run(String... args) throws IOException { } } - private static void parseArgs(String[] argsArray) { + private void parseArgs(String[] argsArray) { List args = new ArrayList<>(Arrays.asList(argsArray)); while (!args.isEmpty()) { + if (!args.get(0).startsWith("-")) { + if (isdAs == null) { + isdAs = parseIsdAs(args); + continue; + } + throw new ExitCodeException(2, Errors.UNEXPECTED_NON_FLAG + args.get(0)); + } + switch (args.get(0)) { case "-e": case "--extended": @@ -86,6 +95,9 @@ private static void parseArgs(String[] argsArray) { case "--local": localIP = parseIP("local", args); break; + case "--log.level": + parseAndSetLogLevel(args); + break; case "-m": case "--maxpaths": maxPaths = parseInt("--maxpaths", args); @@ -94,12 +106,7 @@ private static void parseArgs(String[] argsArray) { daemon = parseAddress("sciond", args); break; default: - if (isdAs == null) { - isdAs = parseIsdAs(args); - continue; - } else { - throw new ExitCodeException(2, "Unknown option: " + args.get(0)); - } + throw new ExitCodeException(2, Errors.UNKNOWN_OPTION + args.get(0)); } args.remove(0); } @@ -108,7 +115,7 @@ private static void parseArgs(String[] argsArray) { } } - public static void run() throws IOException { + public void run() throws IOException { ScionService service = Scion.defaultService(); // dummy address InetSocketAddress destinationAddress = @@ -117,7 +124,7 @@ public static void run() throws IOException { if (paths.isEmpty()) { String src = ScionUtil.toStringIA(service.getLocalIsdAs()); String dst = ScionUtil.toStringIA(isdAs); - throw new IOException("No path found from " + src + " to " + dst); + throw new ExitCodeException(1, "No path found from " + src + " to " + dst); } println("Available paths to " + ScionUtil.toStringIA(isdAs)); @@ -150,11 +157,6 @@ public static void run() throws IOException { println(header + compact); } } - - if (paths.isEmpty()) { - throw new ExitCodeException( - 1, "No paths found to destination: " + ScionUtil.toStringIA(isdAs)); - } } private static void printExtended(Path path, String localIP) { diff --git a/src/main/java/org/scion/cli/Traceroute.java b/src/main/java/org/scion/cli/Traceroute.java index 7baf01b..aeb1181 100644 --- a/src/main/java/org/scion/cli/Traceroute.java +++ b/src/main/java/org/scion/cli/Traceroute.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.scion.cli.util.Errors; import org.scion.cli.util.ExitCodeException; import org.scion.jpan.*; import org.scion.jpan.internal.ScionAddress; @@ -31,20 +32,20 @@ */ public class Traceroute { - private static Integer localPort; - private static boolean startShim = false; - private static long localIsdAs = 0; - private static InetAddress localIP = null; - private static ScionAddress dstAddress; - private static String dstUrl; - private static InetSocketAddress daemon; - private static int timeoutMs = 1000; + private Integer localPort; + private boolean startShim = false; + private long localIsdAs = 0; + private InetAddress localIP = null; + private ScionAddress dstAddress; + private String dstUrl; + private InetSocketAddress daemon; + private int timeoutMs = 1000; public static void main(String... args) { - handleExit(() -> run(args)); + handleExit(() -> new Traceroute().run(args)); } - public static void run(String... args) throws IOException { + public void run(String... args) throws IOException { parseArgs(args); System.setProperty(Constants.PROPERTY_SHIM, startShim ? "true" : "false"); // disable SHIM if (daemon != null) { @@ -57,9 +58,17 @@ public static void run(String... args) throws IOException { } } - private static void parseArgs(String[] argsArray) { + private void parseArgs(String[] argsArray) { List args = new ArrayList<>(Arrays.asList(argsArray)); while (!args.isEmpty()) { + if (!args.get(0).startsWith("-")) { + if (dstAddress == null) { + dstAddress = parseScionAddress(args); + continue; + } + throw new ExitCodeException(2, Errors.UNEXPECTED_NON_FLAG + args.get(0)); + } + switch (args.get(0)) { case "-h": case "--help": @@ -73,6 +82,9 @@ private static void parseArgs(String[] argsArray) { case "--local": localIP = parseIP("local", args); break; + case "--log.level": + parseAndSetLogLevel(args); + break; case "--port": localPort = parseInt("port", args); break; @@ -89,20 +101,13 @@ private static void parseArgs(String[] argsArray) { dstUrl = parseString("url", args); break; default: - if (dstAddress == null) { - dstAddress = parseScionAddress(args); - if (dstAddress != null) { - args.remove(0); - continue; - } - } - throw new ExitCodeException(2, "Unknown option: " + args.get(0)); + throw new ExitCodeException(2, Errors.UNKNOWN_OPTION + args.get(0)); } args.remove(0); } } - public static void run() throws IOException { + public void run() throws IOException { ScionService service = Scion.defaultService(); List paths; diff --git a/src/main/java/org/scion/cli/util/Errors.java b/src/main/java/org/scion/cli/util/Errors.java new file mode 100644 index 0000000..f6e9fe4 --- /dev/null +++ b/src/main/java/org/scion/cli/util/Errors.java @@ -0,0 +1,21 @@ +// 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; + +public class Errors { + public static final String PARSING_ADDRESS = "ERROR parsing address: "; + public static final String UNEXPECTED_NON_FLAG = "Unexpected non-flag arguments: "; + public static final String UNKNOWN_OPTION = "Unknown option: "; +} diff --git a/src/main/java/org/scion/cli/util/Util.java b/src/main/java/org/scion/cli/util/Util.java index 45e5878..769babc 100644 --- a/src/main/java/org/scion/cli/util/Util.java +++ b/src/main/java/org/scion/cli/util/Util.java @@ -14,6 +14,8 @@ package org.scion.cli.util; +import static org.scion.cli.util.Errors.*; + import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -113,7 +115,6 @@ public static void handleExit(Runner runner) { } System.exit(e.exitCode()); } catch (Exception e) { - e.printStackTrace(); System.exit(2); } } @@ -199,11 +200,12 @@ public static ScionAddress parseScionAddress(List args) { byte[] addrBytes = IPHelper.toByteArray(addrStr); check(addrBytes != null, "Address string is not a legal address"); InetAddress inetAddr = InetAddress.getByAddress(addrStr, addrBytes); - return ScionAddress.create(isdIa, inetAddr); + ScionAddress scionAddress = ScionAddress.create(isdIa, inetAddr); + args.remove(0); + return scionAddress; } catch (IndexOutOfBoundsException | IllegalArgumentException | UnknownHostException e) { - println("ERROR parsing address " + s + ": error=\"" + e.getMessage() + "\""); + throw new ExitCodeException(2, PARSING_ADDRESS + s + ": error=\"" + e.getMessage() + "\""); } - return null; } private static void check(boolean pass, String msg) { @@ -211,4 +213,19 @@ private static void check(boolean pass, String msg) { throw new IllegalArgumentException(msg); } } + + public static void parseAndSetLogLevel(List args) { + String ll = parseString("--log.level", args).toUpperCase(); + switch (ll) { + case "ERROR": + case "INFO": + case "WARN": + case "DEBUG": + System.out.println("------------ log level: " + ll); + System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, ll); + break; + default: + throw new ExitCodeException(2, "Illegal log level: \"" + ll + "\""); + } + } } diff --git a/src/test/java/org/scion/cli/AddressTest.java b/src/test/java/org/scion/cli/AddressTest.java new file mode 100644 index 0000000..90799d4 --- /dev/null +++ b/src/test/java/org/scion/cli/AddressTest.java @@ -0,0 +1,40 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.Errors; +import org.scion.cli.util.ExitCodeException; + +class AddressTest { + + @Test + void main_error() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("address", "1-123,127.0.0.1")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains(Errors.UNKNOWN_OPTION)); + } + + @Test + void logLevel_error() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("address", "--log.level", "boring")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("BORING")); + } +} diff --git a/src/test/java/org/scion/cli/CliTest.java b/src/test/java/org/scion/cli/CliTest.java new file mode 100644 index 0000000..435454f --- /dev/null +++ b/src/test/java/org/scion/cli/CliTest.java @@ -0,0 +1,39 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.ExitCodeException; + +class CliTest { + + @Test + void error_noCommand() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run()); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("Invalid number of arguments.")); + } + + @Test + void error_badCommand() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("heeelp")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("heeelp")); + } +} diff --git a/src/test/java/org/scion/cli/PingResponderTest.java b/src/test/java/org/scion/cli/PingResponderTest.java new file mode 100644 index 0000000..244648c --- /dev/null +++ b/src/test/java/org/scion/cli/PingResponderTest.java @@ -0,0 +1,40 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.Errors; +import org.scion.cli.util.ExitCodeException; + +class PingResponderTest { + + @Test + void main_error() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("pr", "1-123,127.0.0.1")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains(Errors.UNKNOWN_OPTION)); + } + + @Test + void logLevel_error() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("pr", "--log.level", "boring")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("BORING")); + } +} diff --git a/src/test/java/org/scion/cli/PingTest.java b/src/test/java/org/scion/cli/PingTest.java new file mode 100644 index 0000000..be77123 --- /dev/null +++ b/src/test/java/org/scion/cli/PingTest.java @@ -0,0 +1,50 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.ExitCodeException; + +class PingTest { + + @Test + void main_noArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("ping")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("missing address or --url")); + } + + @Test + void main_badArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("ping", "1-123---127.0.0.1")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("ERROR parsing address: ")); + } + + @Test + void logLevel_error() { + ExitCodeException e; + e = + assertThrows( + ExitCodeException.class, + () -> Cli.run("ping", "1-123,127.0.0.1", "--log.level", "boring")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("BORING")); + } +} diff --git a/src/test/java/org/scion/cli/ShowpathsTest.java b/src/test/java/org/scion/cli/ShowpathsTest.java new file mode 100644 index 0000000..20f621d --- /dev/null +++ b/src/test/java/org/scion/cli/ShowpathsTest.java @@ -0,0 +1,49 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.ExitCodeException; + +class ShowpathsTest { + + @Test + void main_noArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("showpaths")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("Please provide a destination ISD/AS")); + } + + @Test + void main_badArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("showpaths", "1-123:3-2")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("ISD/AS code is invalid")); + } + + @Test + void logLevel_error() { + ExitCodeException e; + e = + assertThrows( + ExitCodeException.class, () -> Cli.run("showpaths", "1-123", "--log.level", "boring")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("BORING")); + } +} diff --git a/src/test/java/org/scion/cli/TracerouteTest.java b/src/test/java/org/scion/cli/TracerouteTest.java new file mode 100644 index 0000000..e551512 --- /dev/null +++ b/src/test/java/org/scion/cli/TracerouteTest.java @@ -0,0 +1,50 @@ +// 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; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.scion.cli.util.ExitCodeException; + +class TracerouteTest { + + @Test + void main_noArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("tr")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("missing address or --url")); + } + + @Test + void main_badArg() { + ExitCodeException e; + e = assertThrows(ExitCodeException.class, () -> Cli.run("tr", "1-123:4,127.0.0.1")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("ERROR parsing address")); + } + + @Test + void logLevel_error() { + ExitCodeException e; + e = + assertThrows( + ExitCodeException.class, + () -> Cli.run("traceroute", "1-123,127.0.0.1", "--log.level", "boring")); + assertEquals(2, e.exitCode()); + assertTrue(e.getMessage().contains("BORING")); + } +} From 5064a844903875be4aee9ccf3979ec4102ad7571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= Date: Mon, 16 Mar 2026 16:29:08 +0100 Subject: [PATCH 2/3] Logging level and other stuff --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 777339f..b3b683d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ java -jar jpan-cli.jar help * In JPAN 0.6.1, the SCMP Responder will always return packets to 30041, regardless of their actual source port, see JPAN issue [#233](https://github.com/scionproto-contrib/jpan/issues/233). -* `showpaths` flags https://github.com/netsec-ethz/jpan-cli/issues/2 +* jpan-cli should either start on 30041 or start a SHIM (if dispatcher portrange is limited) # Troubleshooting From e05e36948e08949d1e0a143ea40ab8bea78909ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= Date: Mon, 16 Mar 2026 16:50:08 +0100 Subject: [PATCH 3/3] Logging level and other stuff --- src/main/java/org/scion/cli/Ping.java | 7 ++- src/main/java/org/scion/cli/Traceroute.java | 7 ++- src/test/java/org/scion/cli/ScionMock.java | 28 +++++++++ .../resources/topology-scionproto-0.11.json | 62 +++++++++++++++++++ 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/scion/cli/ScionMock.java create mode 100644 src/test/resources/topology-scionproto-0.11.json diff --git a/src/main/java/org/scion/cli/Ping.java b/src/main/java/org/scion/cli/Ping.java index 93b9032..d3a7415 100644 --- a/src/main/java/org/scion/cli/Ping.java +++ b/src/main/java/org/scion/cli/Ping.java @@ -56,16 +56,17 @@ public void run(String... args) throws IOException { if (daemon != null) { System.setProperty(Constants.PROPERTY_DAEMON, daemon.toString()); } + if (dstUrl == null && dstAddress == null) { + throw new ExitCodeException(2, "Error: missing address or --url"); + } try { ScionService service = Scion.defaultService(); if (dstUrl != null) { run(service.lookupPaths(dstUrl, Constants.SCMP_PORT)); - } else if (dstAddress != null) { + } else { run( service.getPaths( dstAddress.getIsdAs(), dstAddress.getInetAddress(), Constants.SCMP_PORT)); - } else { - throw new ExitCodeException(2, "Error: missing address or --url"); } } finally { Scion.closeDefault(); diff --git a/src/main/java/org/scion/cli/Traceroute.java b/src/main/java/org/scion/cli/Traceroute.java index aeb1181..74a5ee2 100644 --- a/src/main/java/org/scion/cli/Traceroute.java +++ b/src/main/java/org/scion/cli/Traceroute.java @@ -51,6 +51,9 @@ public void run(String... args) throws IOException { if (daemon != null) { System.setProperty(Constants.PROPERTY_DAEMON, daemon.toString()); } + if (dstUrl == null && dstAddress == null) { + throw new ExitCodeException(2, "Error: missing address or --url"); + } try { run(); } finally { @@ -113,11 +116,9 @@ public void run() throws IOException { List paths; if (dstUrl != null) { paths = service.lookupPaths(dstUrl, Constants.SCMP_PORT); - } else if (dstAddress != null) { + } else { paths = service.getPaths(dstAddress.getIsdAs(), dstAddress.getInetAddress(), Constants.SCMP_PORT); - } else { - throw new ExitCodeException(2, "Error: missing address or --url"); } if (paths.isEmpty()) { diff --git a/src/test/java/org/scion/cli/ScionMock.java b/src/test/java/org/scion/cli/ScionMock.java new file mode 100644 index 0000000..0a6c404 --- /dev/null +++ b/src/test/java/org/scion/cli/ScionMock.java @@ -0,0 +1,28 @@ +// 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; + +import org.scion.jpan.ScionService; + +public class ScionMock extends ScionService { + + public ScionService create() { + return new ScionMock("topologies/topology-scionproto-0.10.json", Mode.BOOTSTRAP_TOPO_FILE); + } + + protected ScionMock(String addressOrHost, Mode mode) { + super(addressOrHost, mode); + } +} diff --git a/src/test/resources/topology-scionproto-0.11.json b/src/test/resources/topology-scionproto-0.11.json new file mode 100644 index 0000000..28463f7 --- /dev/null +++ b/src/test/resources/topology-scionproto-0.11.json @@ -0,0 +1,62 @@ +{ + "attributes": [ + "core" + ], + "isd_as": "1-ff00:0:110", + "mtu": 1400, + "dispatched_ports": "31000-32767", + "control_service": { + "cs1-ff00_0_110-1": { + "addr": "127.0.0.11:31000" + } + }, + "discovery_service": { + "cs1-ff00_0_110-1": { + "addr": "127.0.0.11:31000" + } + }, + "border_routers": { + "br1-ff00_0_110-1": { + "internal_addr": "127.0.0.9:31002", + "interfaces": { + "1": { + "underlay": { + "local": "127.0.0.4:50000", + "remote": "127.0.0.5:50000" + }, + "isd_as": "1-ff00:0:111", + "link_to": "child", + "mtu": 1280 + } + } + }, + "br1-ff00_0_110-2": { + "internal_addr": "127.0.0.10:31004", + "interfaces": { + "2": { + "underlay": { + "local": "[fd00:f00d:cafe::7f00:4]:50000", + "remote": "[fd00:f00d:cafe::7f00:5]:50000" + }, + "isd_as": "1-ff00:0:112", + "link_to": "child", + "mtu": 1472 + } + } + }, + "br1-ff00_0_110-3": { + "internal_addr": "127.0.0.10:31014", + "interfaces": { + "22": { + "underlay": { + "local": "[fd00:f00d:cafe::7f00:6]:50000", + "remote": "[fd00:f00d:cafe::7f00:7]:50000" + }, + "isd_as": "1-ff00:0:112", + "link_to": "child", + "mtu": 1472 + } + } + } + } +}