diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71e060ea..08564418 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
-## Added
+### Added
- SCMP echo responder [#78](https://github.com/scionproto-contrib/jpan/pull/78)
- Maven Java executor [#80](https://github.com/scionproto-contrib/jpan/pull/80)
- Dev environment setup hints doc [#82](https://github.com/scionproto-contrib/jpan/pull/82)
@@ -19,6 +19,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Rename `DatagramSocket` to `ScionDatagramSopcketl` and move it to main package
- Rename `ScionSocketOptions` starting with `SN_` to `SCION_`
- SCMP API changes. [#71](https://github.com/scionproto-contrib/jpan/pull/71)
+- Make `Path` inherit `InetSocketAddress` [#69](https://github.com/scionproto-contrib/jpan/pull/69)
+ - **BREAKING CHANGE**: Access to path details such as metadata has been moved to
+ `Path.getDetails()`.
+ - **BREAKING CHANGE**: ScionDatagramChannel.send(buffer, path) returns 'int'.
+TODO
+- Move expiryMargin to ScionService?
+- remove ScionAddress?
+- Remove getPaths(long dstIsdAs, InetSocketAddress dstScionAddress) -< ISD + Scion address!!!
+- Remove use of getHostName() -> InetAddress!
### Fixed
- Fixed locking and resizing of buffers. [#68](https://github.com/scionproto-contrib/jpan/pull/68)
diff --git a/doc/Design.md b/doc/Design.md
index fdca7330..80114515 100644
--- a/doc/Design.md
+++ b/doc/Design.md
@@ -64,15 +64,35 @@ used or returned by `Scion.defaultService()`.
## Paths
-There are two types of paths (both inherit `Path`:
+`Path` contains a destination IA:IP:port, a raw path and a first hop IP:port.
+There are two subclasses of paths (both inherit `Path`):
+
- `RequestPath` are used to send initial request. They are retrieved from a path service and contain
meta information.
-- `ResponsePath` are used to respond to a client request. They are extracted from SCION packets.
+- `ResponsePath` are used to respond to a client request. They are extracted from SCION packets.
+ They also contains origin ISD/AS:IP:port.
-`Path` contains a destination IA:IP:port, a raw path and a first hop IP:port.
-`RequestPath` also contains path meta information.
-`ResponsePath` also contains origin IA:IP:port.
+### Design choices - Subclassing InetSocketAddress
+
+`Path` is a subclass of `InetSocketAddress`. This is useful for `DatagramChannel`'s `receive()`
+(which returns an `InetScoketAddress`) and `send()` which requires an `InetSocketAddress` as
+argument.
+
+The catch here is that `Path` has `getAddress()` and `getPort()` which are now contracted to
+return the *remote* IP/port. Ideally they would be called `getRemoteAddress()` and`getRemotePort()`
+but that is not possible.
+
+### Design choices - Mutable & separate "details"
+
+For concurrent usage, `Path`s should be thread safe. THis would ideally be achieved by making them
+immutable.
+The problem is that paths expire or can otherwise be invalidated. We could make this explicit and
+force API users to either manage expiry/invalidation manually.
+
+The alternative is to move the path of `Path` that is mutable to a new class `PathDetails`.
+`PathDetails` is itself immutable and is referenced atomically from `Path`, so thread safe
+behaviour is still easily achievable.
## DatagramSocket
diff --git a/src/main/java/org/scion/jpan/AbstractDatagramChannel.java b/src/main/java/org/scion/jpan/AbstractDatagramChannel.java
index 3c3147bc..5b61d062 100644
--- a/src/main/java/org/scion/jpan/AbstractDatagramChannel.java
+++ b/src/main/java/org/scion/jpan/AbstractDatagramChannel.java
@@ -22,7 +22,6 @@
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NotYetConnectedException;
-import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -179,21 +178,18 @@ public InetSocketAddress getLocalAddress() throws IOException {
/**
* Returns the remote address.
*
+ * @return The remote address or 'null' if this channel is not connected.
* @see DatagramChannel#getRemoteAddress()
- * @return The remote address.
+ * @see #connect(SocketAddress)
+ * @see #connect(RequestPath)
* @throws IOException If an I/O error occurs
*/
public InetSocketAddress getRemoteAddress() throws IOException {
- Path path = getConnectionPath();
- if (path != null) {
- return new InetSocketAddress(path.getRemoteAddress(), path.getRemotePort());
- }
- return null;
+ return (InetSocketAddress) connectionPath;
}
public void disconnect() throws IOException {
synchronized (stateLock) {
- channel.disconnect(); // TODO Why ? We shouldn´t do that...?
connectionPath = null;
}
}
@@ -214,12 +210,15 @@ public void close() throws IOException {
}
/**
- * Connect to a destination host. Note: - A SCION channel will internally connect to the next
- * border router (first hop) instead of the remote host.
+ * Connect to a destination host.
+ *
+ *
NB: A SCION channel will internally connect to the next border router (first hop) instead of
+ * the remote host.
*
- *
NB: This method does internally no call {@link java.nio.channels.DatagramChannel}.connect(),
- * instead it calls bind(). That means this method does NOT perform any additional security checks
- * associated with connect(), only those associated with bind().
+ *
NB: This method does internally not call {@link
+ * java.nio.channels.DatagramChannel}.connect(), instead it calls bind(). That means this method
+ * does NOT perform any additional security checks associated with connect(), only those
+ * associated with bind().
*
* @param addr Address of remote host.
* @return This channel.
@@ -232,7 +231,10 @@ public C connect(SocketAddress addr) throws IOException {
throw new IllegalArgumentException(
"connect() requires an InetSocketAddress or a ScionSocketAddress.");
}
- return connect(pathPolicy.filter(getOrCreateService().getPaths((InetSocketAddress) addr)));
+ if (addr instanceof RequestPath) {
+ return connect(((RequestPath) addr));
+ }
+ return connect(getOrCreateService().lookupAndGetPath((InetSocketAddress) addr, pathPolicy));
}
}
@@ -301,7 +303,7 @@ public C connect(RequestPath path) throws IOException {
*
* @return the current Path or `null` if not path is connected.
*/
- public Path getConnectionPath() {
+ public RequestPath getConnectionPath() {
synchronized (stateLock) {
return connectionPath;
}
@@ -398,17 +400,12 @@ public void setOverrideSourceAddress(InetSocketAddress address) {
this.overrideExternalAddress = address;
}
- protected int sendRaw(ByteBuffer buffer, InetSocketAddress address, Path path)
- throws IOException {
- if (cfgRemoteDispatcher && path != null && path.getRawPath().length == 0) {
- return channel.send(
- buffer, new InetSocketAddress(address.getAddress(), Constants.DISPATCHER_PORT));
+ protected int sendRaw(ByteBuffer buffer, Path path) throws IOException {
+ if (cfgRemoteDispatcher && path.getRawPath().length == 0) {
+ InetAddress remoteHostIP = path.getFirstHopAddress().getAddress();
+ return channel.send(buffer, new InetSocketAddress(remoteHostIP, Constants.DISPATCHER_PORT));
}
- return channel.send(buffer, address);
- }
-
- protected int sendRaw(ByteBuffer buffer, InetSocketAddress address) throws IOException {
- return sendRaw(buffer, address, null);
+ return channel.send(buffer, path.getFirstHopAddress());
}
public Consumer setScmpErrorListener(Consumer listener) {
@@ -536,18 +533,21 @@ protected final ByteBuffer getBufferReceive(int requiredSize) {
/**
* @param path path
* @param payloadLength payload length
- * @return argument path or a new path if the argument path was expired
* @throws IOException in case of IOException.
*/
- protected Path checkPathAndBuildHeader(
+ protected void checkPathAndBuildHeader(
ByteBuffer buffer, Path path, int payloadLength, InternalConstants.HdrTypes hdrType)
throws IOException {
synchronized (stateLock) {
if (path instanceof RequestPath) {
- path = ensureUpToDate((RequestPath) path);
+ RequestPath requestPath = (RequestPath) path;
+ ScionService svc = getOrCreateService();
+ if (svc.refreshPath(requestPath, getPathPolicy(), cfgExpirationSafetyMargin)
+ && isConnected()) {
+ updateConnection(requestPath, true);
+ }
}
buildHeader(buffer, path, payloadLength, hdrType);
- return path;
}
}
@@ -574,6 +574,7 @@ protected void buildHeader(
srcIA = rPath.getLocalIsdAs();
srcAddress = rPath.getLocalAddress();
srcPort = rPath.getLocalPort();
+ // TODO do we need to verify that the srcAddress is equal to the used local address?
} else {
srcIA = getOrCreateService().getLocalIsdAs();
if (overrideExternalAddress != null) {
@@ -623,20 +624,6 @@ protected void buildHeader(
}
}
- protected RequestPath ensureUpToDate(RequestPath path) throws IOException {
- synchronized (stateLock) {
- if (Instant.now().getEpochSecond() + cfgExpirationSafetyMargin <= path.getExpiration()) {
- return path;
- }
- // expired, get new path
- RequestPath newPath = pathPolicy.filter(getOrCreateService().getPaths(path));
- if (isConnected()) {
- updateConnection(newPath, true);
- }
- return newPath;
- }
- }
-
private void updateConnection(RequestPath newPath, boolean mustBeConnected) throws IOException {
if (mustBeConnected && !isConnected()) {
throw new IllegalStateException();
diff --git a/src/main/java/org/scion/jpan/Path.java b/src/main/java/org/scion/jpan/Path.java
index 898619ce..6a981c0c 100644
--- a/src/main/java/org/scion/jpan/Path.java
+++ b/src/main/java/org/scion/jpan/Path.java
@@ -15,55 +15,24 @@
package org.scion.jpan;
import java.net.*;
-import java.util.Arrays;
/**
* A Path is an InetSocketAddress/ISD/AS of a destination host plus a path to that host.
*
* This class is thread safe.
*/
-public abstract class Path {
- private final byte[] pathRaw;
- private final long dstIsdAs;
- private final InetAddress dstAddress;
- private final int dstPort;
+public interface Path {
- protected Path(byte[] rawPath, long dstIsdAs, InetAddress dstIP, int dstPort) {
- this.pathRaw = rawPath;
- this.dstIsdAs = dstIsdAs;
- this.dstAddress = dstIP;
- this.dstPort = dstPort;
- }
+ byte[] getRawPath();
- public byte[] getRawPath() {
- return pathRaw;
- }
+ InetSocketAddress getFirstHopAddress() throws UnknownHostException;
- public abstract InetSocketAddress getFirstHopAddress() throws UnknownHostException;
+ int getRemotePort();
- public int getRemotePort() {
- return dstPort;
- }
+ InetAddress getRemoteAddress();
- public InetAddress getRemoteAddress() {
- return dstAddress;
- }
-
- public long getRemoteIsdAs() {
- return dstIsdAs;
- }
+ long getRemoteIsdAs();
@Override
- public String toString() {
- return "Path{"
- + "rmtIsdAs="
- + ScionUtil.toStringIA(dstIsdAs)
- + ", rmtAddress="
- + dstAddress
- + ", rmtPort="
- + dstPort
- + ", pathRaw="
- + Arrays.toString(pathRaw)
- + '}';
- }
+ String toString();
}
diff --git a/src/main/java/org/scion/jpan/PathDetails.java b/src/main/java/org/scion/jpan/PathDetails.java
new file mode 100644
index 00000000..2ff6ef8f
--- /dev/null
+++ b/src/main/java/org/scion/jpan/PathDetails.java
@@ -0,0 +1,300 @@
+// 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;
+
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.scion.jpan.internal.IPHelper;
+import org.scion.jpan.proto.daemon.Daemon;
+
+/**
+ * PathDetails contains the raw path and meta information such as bandwidth, latency or geo
+ * coordinates. PathDetails is available from RequestPaths that are created/returned by the
+ * ScionService when requesting a new path from the control service.
+ */
+public class PathDetails {
+
+ private final Daemon.Path pathProtoc;
+ private final byte[] pathRaw;
+ // We store the first hop separately to void creating unnecessary objects.
+ private final InetSocketAddress firstHop;
+
+ static PathDetails create(Daemon.Path path, InetAddress dstIP, int dstPort) {
+ return new PathDetails(path, dstIP, dstPort);
+ }
+
+ private PathDetails(Daemon.Path path, InetAddress dstIP, int dstPort) {
+ this.pathProtoc = path;
+ this.pathRaw = path.getRaw().toByteArray();
+ if (getRawPath().length == 0) {
+ // local AS has path length 0
+ firstHop = new InetSocketAddress(dstIP, dstPort);
+ } else {
+ firstHop = getFirstHopAddress(path);
+ }
+ }
+
+ private InetSocketAddress getFirstHopAddress(Daemon.Path internalPath) {
+ try {
+ String underlayAddressString = internalPath.getInterface().getAddress().getAddress();
+ int splitIndex = underlayAddressString.indexOf(':');
+ InetAddress ip = IPHelper.toInetAddress(underlayAddressString.substring(0, splitIndex));
+ int port = Integer.parseUnsignedInt(underlayAddressString.substring(splitIndex + 1));
+ return new InetSocketAddress(ip, port);
+ } catch (UnknownHostException e) {
+ // This really should never happen, the first hop is a literal IP address.
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private Daemon.Path protoPath() {
+ if (pathProtoc == null) {
+ throw new IllegalStateException(
+ "Information is only available for paths that"
+ + " were retrieved directly from a path server.");
+ }
+ return pathProtoc;
+ }
+
+ public InetSocketAddress getFirstHopAddress() throws UnknownHostException {
+ return firstHop;
+ }
+
+ public byte[] getRawPath() {
+ return pathRaw;
+ }
+
+ /**
+ * @return Interface for exiting the local AS using this path.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public Interface getInterface() {
+ return new Interface(protoPath().getInterface());
+ }
+
+ /**
+ * @return The list of interfaces the path is composed of.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getInterfacesList() {
+ return Collections.unmodifiableList(
+ protoPath().getInterfacesList().stream()
+ .map(PathInterface::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * @return The maximum transmission unit (MTU) on the path.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public int getMtu() {
+ return protoPath().getMtu();
+ }
+
+ /**
+ * @return The point in time when this path expires. In seconds since UNIX epoch.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public long getExpiration() {
+ return protoPath().getExpiration().getSeconds();
+ }
+
+ /**
+ * @return Latency lists the latencies between any two consecutive interfaces. Entry i describes
+ * the latency between interface i and i+1. Consequently, there are N-1 entries for N
+ * interfaces. A 0-value indicates that the AS did not announce a latency for this hop.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getLatencyList() {
+ return Collections.unmodifiableList(
+ protoPath().getLatencyList().stream()
+ .map(time -> (int) (time.getSeconds() * 1_000 + time.getNanos() / 1_000_000))
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * @return Bandwidth lists the bandwidth between any two consecutive interfaces, in Kbit/s. Entry
+ * i describes the bandwidth between interfaces i and i+1. A 0-value indicates that the AS did
+ * not announce a bandwidth for this hop.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getBandwidthList() {
+ return protoPath().getBandwidthList();
+ }
+
+ /**
+ * @return Geo lists the geographical position of the border routers along the path. Entry i
+ * describes the position of the router for interface i. A 0-value indicates that the AS did
+ * not announce a position for this router.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getGeoList() {
+ return Collections.unmodifiableList(
+ protoPath().getGeoList().stream().map(GeoCoordinates::new).collect(Collectors.toList()));
+ }
+
+ /**
+ * @return LinkType contains the announced link type of inter-domain links. Entry i describes the
+ * link between interfaces 2*i and 2*i+1.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getLinkTypeList() {
+ return Collections.unmodifiableList(
+ protoPath().getLinkTypeList().stream()
+ .map(linkType -> LinkType.values()[linkType.getNumber()])
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * @return InternalHops lists the number of AS internal hops for the ASes on path. Entry i
+ * describes the hop between interfaces 2*i+1 and 2*i+2 in the same AS. Consequently, there
+ * are no entries for the first and last ASes, as these are not traversed completely by the
+ * path.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getInternalHopsList() {
+ return protoPath().getInternalHopsList();
+ }
+
+ /**
+ * @return Notes contains the notes added by ASes on the path, in the order of occurrence. Entry i
+ * is the note of AS i on the path.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public List getNotesList() {
+ return protoPath().getNotesList();
+ }
+
+ /**
+ * @return EpicAuths contains the EPIC authenticators used to calculate the PHVF and LHVF.
+ * @throws IllegalStateException if this is path is only a raw path
+ */
+ public EpicAuths getEpicAuths() {
+ return new EpicAuths(protoPath().getEpicAuths());
+ }
+
+ public enum LinkType {
+ /** Unspecified link type. */
+ LINK_TYPE_UNSPECIFIED, // = 0
+ /** Direct physical connection. */
+ LINK_TYPE_DIRECT, // = 1
+ /** Connection with local routing/switching. */
+ LINK_TYPE_MULTI_HOP, // = 2
+ /** Connection overlayed over publicly routed Internet. */
+ LINK_TYPE_OPEN_NET, // = 3
+ }
+
+ public static class EpicAuths {
+ private final byte[] authPhvf;
+ private final byte[] authLhvf;
+
+ private EpicAuths(Daemon.EpicAuths epicAuths) {
+ this.authPhvf = epicAuths.getAuthPhvf().toByteArray();
+ this.authLhvf = epicAuths.getAuthLhvf().toByteArray();
+ }
+
+ /**
+ * @return AuthPHVF is the authenticator use to calculate the PHVF.
+ */
+ public byte[] getAuthPhvf() {
+ return authPhvf;
+ }
+
+ /**
+ * @return AuthLHVF is the authenticator use to calculate the LHVF.
+ */
+ public byte[] getAuthLhvf() {
+ return authLhvf;
+ }
+ }
+
+ public static class Interface {
+ private final String address;
+
+ private Interface(Daemon.Interface inter) {
+ this.address = inter.getAddress().getAddress();
+ }
+
+ /**
+ * @return Underlay address to exit through the interface.
+ */
+ public String getAddress() {
+ return address;
+ }
+ }
+
+ public static class PathInterface {
+ private final long isdAs;
+
+ private final long id;
+
+ private PathInterface(Daemon.PathInterface pathInterface) {
+ this.isdAs = pathInterface.getIsdAs();
+ this.id = pathInterface.getId();
+ }
+
+ /**
+ * @return ISD-AS the interface belongs to.
+ */
+ public long getIsdAs() {
+ return isdAs;
+ }
+
+ /**
+ * @return ID of the interface in the AS.
+ */
+ public long getId() {
+ return id;
+ }
+ }
+
+ public static class GeoCoordinates {
+ private final float latitude;
+ private final float longitude;
+ private final String address;
+
+ private GeoCoordinates(Daemon.GeoCoordinates geoCoordinates) {
+ this.latitude = geoCoordinates.getLatitude();
+ this.longitude = geoCoordinates.getLongitude();
+ this.address = geoCoordinates.getAddress();
+ }
+
+ /**
+ * @return Latitude of the geographic coordinate, in the WGS 84 datum.
+ */
+ public float getLatitude() {
+ return latitude;
+ }
+
+ /**
+ * @return Longitude of the geographic coordinate, in the WGS 84 datum.
+ */
+ public float getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * @return Civic address of the location.
+ */
+ public String getAddress() {
+ return address;
+ }
+ }
+}
diff --git a/src/main/java/org/scion/jpan/PathImpl.java b/src/main/java/org/scion/jpan/PathImpl.java
new file mode 100644
index 00000000..a56ad2ae
--- /dev/null
+++ b/src/main/java/org/scion/jpan/PathImpl.java
@@ -0,0 +1,70 @@
+// 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;
+
+import java.net.*;
+import java.util.Arrays;
+
+/**
+ * A Path is an InetSocketAddress/ISD/AS of a destination host plus a path to that host.
+ *
+ * This class is thread safe.
+ */
+abstract class PathImpl extends InetSocketAddress implements Path {
+ private final long dstIsdAs;
+
+ protected PathImpl(long dstIsdAs, InetAddress dstIP, int dstPort) {
+ super(dstIP, dstPort);
+ this.dstIsdAs = dstIsdAs;
+ }
+
+ public abstract byte[] getRawPath();
+
+ public abstract InetSocketAddress getFirstHopAddress() throws UnknownHostException;
+
+ @Deprecated // Use getPort() instead
+ public int getRemotePort() {
+ return getPort();
+ }
+
+ @Deprecated // Use getAddress() instead
+ public InetAddress getRemoteAddress() {
+ return getAddress();
+ }
+
+ @Deprecated // Use getIsdAs() instead
+ public long getRemoteIsdAs() {
+ return dstIsdAs;
+ }
+
+ /**
+ * @return ISD/AS of the remote host.
+ */
+ public long getIsdAs() {
+ return dstIsdAs;
+ }
+
+ @Override
+ public String toString() {
+ return "Path{"
+ + "ISD/AS="
+ + ScionUtil.toStringIA(dstIsdAs)
+ + ", address="
+ + super.toString()
+ + ", pathRaw="
+ + Arrays.toString(getRawPath())
+ + '}';
+ }
+}
diff --git a/src/main/java/org/scion/jpan/PathPolicy.java b/src/main/java/org/scion/jpan/PathPolicy.java
index 454d5a83..29feb5e4 100644
--- a/src/main/java/org/scion/jpan/PathPolicy.java
+++ b/src/main/java/org/scion/jpan/PathPolicy.java
@@ -32,7 +32,7 @@ public RequestPath filter(List paths) {
class MaxBandwith implements PathPolicy {
public RequestPath filter(List paths) {
return paths.stream()
- .max(Comparator.comparing(path -> Collections.min(path.getBandwidthList())))
+ .max(Comparator.comparing(path -> Collections.min(path.getDetails().getBandwidthList())))
.orElseThrow(NoSuchElementException::new);
}
}
@@ -45,7 +45,7 @@ public RequestPath filter(List paths) {
.min(
Comparator.comparing(
path ->
- path.getLatencyList().stream()
+ path.getDetails().getLatencyList().stream()
.mapToLong(l -> l > 0 ? l : Integer.MAX_VALUE)
.reduce(0, Long::sum)))
.orElseThrow(NoSuchElementException::new);
@@ -55,7 +55,7 @@ public RequestPath filter(List paths) {
class MinHopCount implements PathPolicy {
public RequestPath filter(List paths) {
return paths.stream()
- .min(Comparator.comparing(path -> path.getInternalHopsList().size()))
+ .min(Comparator.comparing(path -> path.getDetails().getInternalHopsList().size()))
.orElseThrow(NoSuchElementException::new);
}
}
@@ -76,7 +76,7 @@ public RequestPath filter(List paths) {
}
private boolean checkPath(RequestPath path) {
- for (RequestPath.PathInterface pif : path.getInterfacesList()) {
+ for (PathDetails.PathInterface pif : path.getDetails().getInterfacesList()) {
int isd = (int) (pif.getIsdAs() >>> 48);
if (!allowedIsds.contains(isd)) {
return false;
@@ -102,7 +102,7 @@ public RequestPath filter(List paths) {
}
private boolean checkPath(RequestPath path) {
- for (RequestPath.PathInterface pif : path.getInterfacesList()) {
+ for (PathDetails.PathInterface pif : path.getDetails().getInterfacesList()) {
int isd = (int) (pif.getIsdAs() >>> 48);
if (disallowedIsds.contains(isd)) {
return false;
diff --git a/src/main/java/org/scion/jpan/RequestPath.java b/src/main/java/org/scion/jpan/RequestPath.java
index 9e6de75e..3cb4ef75 100644
--- a/src/main/java/org/scion/jpan/RequestPath.java
+++ b/src/main/java/org/scion/jpan/RequestPath.java
@@ -14,283 +14,22 @@
package org.scion.jpan;
-import java.io.UncheckedIOException;
import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.scion.jpan.internal.IPHelper;
import org.scion.jpan.proto.daemon.Daemon;
/**
* A RequestPath is a Path with additional meta information such as bandwidth, latency or geo
* coordinates. RequestPaths are created/returned by the ScionService when requesting a new path
* from the control service.
+ *
+ * A RequestPath is immutable except for an atomic reference to PathDetails (PathDetails are
+ * immutable). The reference may be updated, for example when a path expires.
*/
-public class RequestPath extends Path {
-
- private final Daemon.Path pathProtoc;
- // We store the first hop separately to void creating unnecessary objects.
- private final InetSocketAddress firstHop;
+public interface RequestPath extends Path {
static RequestPath create(Daemon.Path path, long dstIsdAs, InetAddress dstIP, int dstPort) {
- return new RequestPath(path, dstIsdAs, dstIP, dstPort);
- }
-
- private RequestPath(Daemon.Path path, long dstIsdAs, InetAddress dstIP, int dstPort) {
- super(path.getRaw().toByteArray(), dstIsdAs, dstIP, dstPort);
- this.pathProtoc = path;
- if (getRawPath().length == 0) {
- // local AS has path length 0
- firstHop = new InetSocketAddress(getRemoteAddress(), getRemotePort());
- } else {
- firstHop = getFirstHopAddress(pathProtoc);
- }
- }
-
- private Daemon.Path protoPath() {
- if (pathProtoc == null) {
- throw new IllegalStateException(
- "Information is only available for paths that"
- + " were retrieved directly from a path server.");
- }
- return pathProtoc;
- }
-
- @Override
- public InetSocketAddress getFirstHopAddress() throws UnknownHostException {
- return firstHop;
- }
-
- private InetSocketAddress getFirstHopAddress(Daemon.Path internalPath) {
- try {
- String underlayAddressString = internalPath.getInterface().getAddress().getAddress();
- int splitIndex = underlayAddressString.indexOf(':');
- InetAddress ip = IPHelper.toInetAddress(underlayAddressString.substring(0, splitIndex));
- int port = Integer.parseUnsignedInt(underlayAddressString.substring(splitIndex + 1));
- return new InetSocketAddress(ip, port);
- } catch (UnknownHostException e) {
- // This really should never happen, the first hop is a literal IP address.
- throw new UncheckedIOException(e);
- }
- }
-
- /**
- * @return Interface for exiting the local AS using this path.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public Interface getInterface() {
- return new Interface(protoPath().getInterface());
- }
-
- /**
- * @return The list of interfaces the path is composed of.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getInterfacesList() {
- return Collections.unmodifiableList(
- protoPath().getInterfacesList().stream()
- .map(PathInterface::new)
- .collect(Collectors.toList()));
- }
-
- /**
- * @return The maximum transmission unit (MTU) on the path.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public int getMtu() {
- return protoPath().getMtu();
- }
-
- /**
- * @return The point in time when this path expires. In seconds since UNIX epoch.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public long getExpiration() {
- return protoPath().getExpiration().getSeconds();
- }
-
- /**
- * @return Latency lists the latencies between any two consecutive interfaces. Entry i describes
- * the latency between interface i and i+1. Consequently, there are N-1 entries for N
- * interfaces. A 0-value indicates that the AS did not announce a latency for this hop.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getLatencyList() {
- return Collections.unmodifiableList(
- protoPath().getLatencyList().stream()
- .map(time -> (int) (time.getSeconds() * 1_000 + time.getNanos() / 1_000_000))
- .collect(Collectors.toList()));
- }
-
- /**
- * @return Bandwidth lists the bandwidth between any two consecutive interfaces, in Kbit/s. Entry
- * i describes the bandwidth between interfaces i and i+1. A 0-value indicates that the AS did
- * not announce a bandwidth for this hop.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getBandwidthList() {
- return protoPath().getBandwidthList();
- }
-
- /**
- * @return Geo lists the geographical position of the border routers along the path. Entry i
- * describes the position of the router for interface i. A 0-value indicates that the AS did
- * not announce a position for this router.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getGeoList() {
- return Collections.unmodifiableList(
- protoPath().getGeoList().stream().map(GeoCoordinates::new).collect(Collectors.toList()));
- }
-
- /**
- * @return LinkType contains the announced link type of inter-domain links. Entry i describes the
- * link between interfaces 2*i and 2*i+1.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getLinkTypeList() {
- return Collections.unmodifiableList(
- protoPath().getLinkTypeList().stream()
- .map(linkType -> LinkType.values()[linkType.getNumber()])
- .collect(Collectors.toList()));
- }
-
- /**
- * @return InternalHops lists the number of AS internal hops for the ASes on path. Entry i
- * describes the hop between interfaces 2*i+1 and 2*i+2 in the same AS. Consequently, there
- * are no entries for the first and last ASes, as these are not traversed completely by the
- * path.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getInternalHopsList() {
- return protoPath().getInternalHopsList();
+ return RequestPathImpl.create(path, dstIsdAs, dstIP, dstPort);
}
- /**
- * @return Notes contains the notes added by ASes on the path, in the order of occurrence. Entry i
- * is the note of AS i on the path.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public List getNotesList() {
- return protoPath().getNotesList();
- }
-
- /**
- * @return EpicAuths contains the EPIC authenticators used to calculate the PHVF and LHVF.
- * @throws IllegalStateException if this is path is only a raw path
- */
- public EpicAuths getEpicAuths() {
- return new EpicAuths(protoPath().getEpicAuths());
- }
-
- public enum LinkType {
- /** Unspecified link type. */
- LINK_TYPE_UNSPECIFIED, // = 0
- /** Direct physical connection. */
- LINK_TYPE_DIRECT, // = 1
- /** Connection with local routing/switching. */
- LINK_TYPE_MULTI_HOP, // = 2
- /** Connection overlayed over publicly routed Internet. */
- LINK_TYPE_OPEN_NET, // = 3
- }
-
- public static class EpicAuths {
- private final byte[] authPhvf;
- private final byte[] authLhvf;
-
- private EpicAuths(Daemon.EpicAuths epicAuths) {
- this.authPhvf = epicAuths.getAuthPhvf().toByteArray();
- this.authLhvf = epicAuths.getAuthLhvf().toByteArray();
- }
-
- /**
- * @return AuthPHVF is the authenticator use to calculate the PHVF.
- */
- public byte[] getAuthPhvf() {
- return authPhvf;
- }
-
- /**
- * @return AuthLHVF is the authenticator use to calculate the LHVF.
- */
- public byte[] getAuthLhvf() {
- return authLhvf;
- }
- }
-
- public static class Interface {
- private final String address;
-
- private Interface(Daemon.Interface inter) {
- this.address = inter.getAddress().getAddress();
- }
-
- /**
- * @return Underlay address to exit through the interface.
- */
- public String getAddress() {
- return address;
- }
- }
-
- public static class PathInterface {
- private final long isdAs;
-
- private final long id;
-
- private PathInterface(Daemon.PathInterface pathInterface) {
- this.isdAs = pathInterface.getIsdAs();
- this.id = pathInterface.getId();
- }
-
- /**
- * @return ISD-AS the interface belongs to.
- */
- public long getIsdAs() {
- return isdAs;
- }
-
- /**
- * @return ID of the interface in the AS.
- */
- public long getId() {
- return id;
- }
- }
-
- public static class GeoCoordinates {
- private final float latitude;
- private final float longitude;
- private final String address;
-
- private GeoCoordinates(Daemon.GeoCoordinates geoCoordinates) {
- this.latitude = geoCoordinates.getLatitude();
- this.longitude = geoCoordinates.getLongitude();
- this.address = geoCoordinates.getAddress();
- }
-
- /**
- * @return Latitude of the geographic coordinate, in the WGS 84 datum.
- */
- public float getLatitude() {
- return latitude;
- }
-
- /**
- * @return Longitude of the geographic coordinate, in the WGS 84 datum.
- */
- public float getLongitude() {
- return longitude;
- }
-
- /**
- * @return Civic address of the location.
- */
- public String getAddress() {
- return address;
- }
- }
+ PathDetails getDetails();
}
diff --git a/src/main/java/org/scion/jpan/RequestPathImpl.java b/src/main/java/org/scion/jpan/RequestPathImpl.java
new file mode 100644
index 00000000..cd0dc28b
--- /dev/null
+++ b/src/main/java/org/scion/jpan/RequestPathImpl.java
@@ -0,0 +1,61 @@
+// 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;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.concurrent.atomic.AtomicReference;
+import org.scion.jpan.proto.daemon.Daemon;
+
+/**
+ * A RequestPath is a Path with additional meta information such as bandwidth, latency or geo
+ * coordinates. RequestPaths are created/returned by the ScionService when requesting a new path
+ * from the control service.
+ *
+ * A RequestPath is immutable except for an atomic reference to PathDetails (PathDetails are
+ * immutable). The reference may be updated, for example when a path expires.
+ */
+class RequestPathImpl extends PathImpl implements RequestPath {
+
+ private final AtomicReference details = new AtomicReference<>();
+
+ static RequestPathImpl create(Daemon.Path path, long dstIsdAs, InetAddress dstIP, int dstPort) {
+ return new RequestPathImpl(path, dstIsdAs, dstIP, dstPort);
+ }
+
+ private RequestPathImpl(Daemon.Path path, long dstIsdAs, InetAddress dstIP, int dstPort) {
+ super(dstIsdAs, dstIP, dstPort);
+ this.details.set(PathDetails.create(path, dstIP, dstPort));
+ }
+
+ @Override
+ public InetSocketAddress getFirstHopAddress() throws UnknownHostException {
+ return getDetails().getFirstHopAddress();
+ }
+
+ @Override
+ public byte[] getRawPath() {
+ return getDetails().getRawPath();
+ }
+
+ public PathDetails getDetails() {
+ return details.get();
+ }
+
+ void setDetails(PathDetails details) {
+ this.details.set(details);
+ }
+}
diff --git a/src/main/java/org/scion/jpan/ResponsePath.java b/src/main/java/org/scion/jpan/ResponsePath.java
index b53b4cc7..8a3ffb45 100644
--- a/src/main/java/org/scion/jpan/ResponsePath.java
+++ b/src/main/java/org/scion/jpan/ResponsePath.java
@@ -22,16 +22,12 @@
* ISD/AS, IP and port of the local host. This is mostly for convenience to avoid looking up this
* information, but it also ensures that the return packet header contains the exact information
* sent/expected by the client.
+ *
+ * A ResponsePath is immutable and thus thread safe.
*/
-public class ResponsePath extends Path {
+public interface ResponsePath extends Path {
- private final InetSocketAddress firstHopAddress;
- // The ResponsePath gets source information from the incoming packet.
- private final long srcIsdAs;
- private final InetAddress srcAddress;
- private final int srcPort;
-
- public static ResponsePath create(
+ static ResponsePath create(
byte[] rawPath,
long srcIsdAs,
InetAddress srcIP,
@@ -40,55 +36,13 @@ public static ResponsePath create(
InetAddress dstIP,
int dstPort,
InetSocketAddress firstHopAddress) {
- return new ResponsePath(
+ return ResponsePathImpl.create(
rawPath, srcIsdAs, srcIP, srcPort, dstIsdAs, dstIP, dstPort, firstHopAddress);
}
- private ResponsePath(
- byte[] rawPath,
- long srcIsdAs,
- InetAddress srcIP,
- int srcPort,
- long dstIsdAs,
- InetAddress dstIP,
- int dstPort,
- InetSocketAddress firstHopAddress) {
- super(rawPath, dstIsdAs, dstIP, dstPort);
- this.firstHopAddress = firstHopAddress;
- this.srcIsdAs = srcIsdAs;
- this.srcAddress = srcIP;
- this.srcPort = srcPort;
- }
-
- @Override
- public InetSocketAddress getFirstHopAddress() {
- return firstHopAddress;
- }
+ long getLocalIsdAs();
- public long getLocalIsdAs() {
- return srcIsdAs;
- }
+ InetAddress getLocalAddress();
- public InetAddress getLocalAddress() {
- return srcAddress;
- }
-
- public int getLocalPort() {
- return srcPort;
- }
-
- @Override
- public String toString() {
- return "ResponsePath{"
- + super.toString()
- + ", firstHopAddress="
- + firstHopAddress
- + ", localIsdAs="
- + srcIsdAs
- + ", localAddress="
- + srcAddress
- + ", localPort="
- + srcPort
- + '}';
- }
+ int getLocalPort();
}
diff --git a/src/main/java/org/scion/jpan/ResponsePathImpl.java b/src/main/java/org/scion/jpan/ResponsePathImpl.java
new file mode 100644
index 00000000..1983efe6
--- /dev/null
+++ b/src/main/java/org/scion/jpan/ResponsePathImpl.java
@@ -0,0 +1,103 @@
+// 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;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+/**
+ * A ResponsePath is created/returned when receiving a packet. Besides being a Path, it contains
+ * ISD/AS, IP and port of the local host. This is mostly for convenience to avoid looking up this
+ * information, but it also ensures that the return packet header contains the exact information
+ * sent/expected by the client.
+ *
+ *
A ResponsePath is immutable and thus thread safe.
+ */
+class ResponsePathImpl extends PathImpl implements ResponsePath {
+
+ private final InetSocketAddress firstHopAddress;
+ // The ResponsePath gets source information from the incoming packet.
+ private final long srcIsdAs;
+ private final InetAddress srcAddress;
+ private final int srcPort;
+ private final byte[] pathRaw;
+
+ public static ResponsePathImpl create(
+ byte[] rawPath,
+ long srcIsdAs,
+ InetAddress srcIP,
+ int srcPort,
+ long dstIsdAs,
+ InetAddress dstIP,
+ int dstPort,
+ InetSocketAddress firstHopAddress) {
+ return new ResponsePathImpl(
+ rawPath, srcIsdAs, srcIP, srcPort, dstIsdAs, dstIP, dstPort, firstHopAddress);
+ }
+
+ private ResponsePathImpl(
+ byte[] rawPath,
+ long srcIsdAs,
+ InetAddress srcIP,
+ int srcPort,
+ long dstIsdAs,
+ InetAddress dstIP,
+ int dstPort,
+ InetSocketAddress firstHopAddress) {
+ super(dstIsdAs, dstIP, dstPort);
+ this.firstHopAddress = firstHopAddress;
+ this.srcIsdAs = srcIsdAs;
+ this.srcAddress = srcIP;
+ this.srcPort = srcPort;
+ this.pathRaw = rawPath;
+ }
+
+ @Override
+ public InetSocketAddress getFirstHopAddress() {
+ return firstHopAddress;
+ }
+
+ public long getLocalIsdAs() {
+ return srcIsdAs;
+ }
+
+ public InetAddress getLocalAddress() {
+ return srcAddress;
+ }
+
+ public int getLocalPort() {
+ return srcPort;
+ }
+
+ @Override
+ public byte[] getRawPath() {
+ return pathRaw;
+ }
+
+ @Override
+ public String toString() {
+ return "ResponsePath{"
+ + super.toString()
+ + ", firstHopAddress="
+ + firstHopAddress
+ + ", localIsdAs="
+ + srcIsdAs
+ + ", localAddress="
+ + srcAddress
+ + ", localPort="
+ + srcPort
+ + '}';
+ }
+}
diff --git a/src/main/java/org/scion/jpan/ScionDatagramChannel.java b/src/main/java/org/scion/jpan/ScionDatagramChannel.java
index 8156d4c2..e9f80ee8 100644
--- a/src/main/java/org/scion/jpan/ScionDatagramChannel.java
+++ b/src/main/java/org/scion/jpan/ScionDatagramChannel.java
@@ -84,20 +84,23 @@ public ResponsePath receive(ByteBuffer userBuffer) throws IOException {
* cannot be resolved to an ISD/AS.
* @see java.nio.channels.DatagramChannel#send(ByteBuffer, SocketAddress)
*/
- public void send(ByteBuffer srcBuffer, SocketAddress destination) throws IOException {
+ public int send(ByteBuffer srcBuffer, SocketAddress destination) throws IOException {
if (!(destination instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Address must be of type InetSocketAddress.");
}
+ if (destination instanceof Path) {
+ return send(srcBuffer, (Path) destination);
+ }
InetSocketAddress dst = (InetSocketAddress) destination;
- Path path = getPathPolicy().filter(getOrCreateService().getPaths(dst));
- send(srcBuffer, path);
+ Path path = getOrCreateService().lookupAndGetPath(dst, getPathPolicy());
+ return send(srcBuffer, path);
}
/**
* Attempts to send the content of the buffer to the destinationAddress.
*
* @param srcBuffer Data to send
- * @param path Path to destination. If this is a Request path and it is expired then it will
+ * @param path Path to destination. If this is a RequestPath, and it is expired, then it will
* automatically be replaced with a new path. Expiration of ResponsePaths is not checked
* @return either the path argument or a new path if the path was an expired RequestPath. Note
* that ResponsePaths are not checked for expiration.
@@ -105,22 +108,22 @@ public void send(ByteBuffer srcBuffer, SocketAddress destination) throws IOExcep
* cannot be resolved to an ISD/AS.
* @see java.nio.channels.DatagramChannel#send(ByteBuffer, SocketAddress)
*/
- public Path send(ByteBuffer srcBuffer, Path path) throws IOException {
+ public int send(ByteBuffer srcBuffer, Path path) throws IOException {
writeLock().lock();
try {
ByteBuffer buffer = getBufferSend(srcBuffer.remaining());
// + 8 for UDP overlay header length
- Path actualPath =
- checkPathAndBuildHeader(
- buffer, path, srcBuffer.remaining() + 8, InternalConstants.HdrTypes.UDP);
+ checkPathAndBuildHeader(
+ buffer, path, srcBuffer.remaining() + 8, InternalConstants.HdrTypes.UDP);
+ int headerSize = buffer.position();
try {
buffer.put(srcBuffer);
} catch (BufferOverflowException e) {
throw new IOException("Packet is larger than max send buffer size.");
}
buffer.flip();
- sendRaw(buffer, actualPath.getFirstHopAddress(), actualPath);
- return actualPath;
+ int size = sendRaw(buffer, path);
+ return size - headerSize;
} finally {
writeLock().unlock();
}
@@ -165,15 +168,16 @@ public int write(ByteBuffer src) throws IOException {
try {
checkOpen();
checkConnected(true);
+ Path path = getConnectionPath();
ByteBuffer buffer = getBufferSend(src.remaining());
int len = src.remaining();
// + 8 for UDP overlay header length
- checkPathAndBuildHeader(buffer, getConnectionPath(), len + 8, InternalConstants.HdrTypes.UDP);
+ checkPathAndBuildHeader(buffer, path, len + 8, InternalConstants.HdrTypes.UDP);
buffer.put(src);
buffer.flip();
- int sent = sendRaw(buffer, getConnectionPath().getFirstHopAddress(), getConnectionPath());
+ int sent = sendRaw(buffer, path);
if (sent < buffer.limit() || buffer.remaining() > 0) {
throw new ScionException("Failed to send all data.");
}
diff --git a/src/main/java/org/scion/jpan/ScionDatagramSocket.java b/src/main/java/org/scion/jpan/ScionDatagramSocket.java
index b4ccca3f..6c2894ba 100644
--- a/src/main/java/org/scion/jpan/ScionDatagramSocket.java
+++ b/src/main/java/org/scion/jpan/ScionDatagramSocket.java
@@ -296,9 +296,10 @@ public void send(DatagramPacket packet) throws IOException {
synchronized (pathCache) {
path = pathCache.get(addr);
if (path == null) {
- path = channel.getPathPolicy().filter(channel.getOrCreateService2().getPaths(addr));
+ path = channel.getOrCreateService().lookupAndGetPath(addr, channel.getPathPolicy());
} else if (path instanceof RequestPath
- && ((RequestPath) path).getExpiration() > Instant.now().getEpochSecond()) {
+ && ((RequestPath) path).getDetails().getExpiration()
+ > Instant.now().getEpochSecond()) {
// check expiration only for RequestPaths
RequestPath request = (RequestPath) path;
path = channel.getPathPolicy().filter(channel.getOrCreateService2().getPaths(request));
@@ -532,7 +533,9 @@ public void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
*
* @return the current Path or `null` if not path is connected.
* @see ScionDatagramChannel#getConnectionPath()
+ * @deprecated Please use getRemoteAddress() instead
*/
+ @Deprecated
public RequestPath getConnectionPath() {
return (RequestPath) channel.getConnectionPath();
}
diff --git a/src/main/java/org/scion/jpan/ScionService.java b/src/main/java/org/scion/jpan/ScionService.java
index f65e8329..84c7598c 100644
--- a/src/main/java/org/scion/jpan/ScionService.java
+++ b/src/main/java/org/scion/jpan/ScionService.java
@@ -33,6 +33,7 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Paths;
+import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -306,8 +307,8 @@ List getPathListDaemon(long srcIsdAs, long dstIsdAs) {
* @return All paths returned by the path service.
* @throws IOException if an errors occurs while querying paths.
*/
+ @Deprecated // Please use lookup() instead
public List getPaths(InetSocketAddress dstAddress) throws IOException {
- // Use getHostString() to avoid DNS reverse lookup.
ScionAddress sa = getScionAddress(dstAddress.getHostString());
return getPaths(sa.getIsdAs(), sa.getInetAddress(), dstAddress.getPort());
}
@@ -320,7 +321,6 @@ public List getPaths(InetSocketAddress dstAddress) throws IOExcepti
* @return All paths returned by the path service.
*/
public List getPaths(long dstIsdAs, InetSocketAddress dstScionAddress) {
- // TODO change method API name to make clear that this requires a SCION IP.
return getPaths(dstIsdAs, dstScionAddress.getAddress(), dstScionAddress.getPort());
}
@@ -338,19 +338,44 @@ public List getPaths(RequestPath path) {
* Request paths from the local ISD/AS to the destination.
*
* @param dstIsdAs Destination ISD/AS
- * @param dstAddress Destination IP address
+ * @param dstAddress A SCION-enabled Destination IP address
* @param dstPort Destination port
- * @return All paths returned by the path service.
+ * @return All paths returned by the path service. Returns an empty list if no paths are found.
*/
public List getPaths(long dstIsdAs, InetAddress dstAddress, int dstPort) {
- long srcIsdAs = getLocalIsdAs();
- List paths = getPathList(srcIsdAs, dstIsdAs);
- if (paths.isEmpty()) {
- return Collections.emptyList();
+ return getPaths(ScionAddress.create(dstIsdAs, dstAddress), dstPort);
+ }
+
+ /**
+ * Resolves the address to a SCION address, request paths, and selects a path using the policy.
+ *
+ * @param dstAddr Destination address
+ * @param policy path policy
+ * @return All paths returned by the path service.
+ * @throws ScionException if the DNS/TXT lookup did not return a (valid) SCION address.
+ */
+ public RequestPath lookupAndGetPath(InetSocketAddress dstAddr, PathPolicy policy)
+ throws ScionException {
+ if (policy == null) {
+ policy = PathPolicy.DEFAULT;
}
+ return policy.filter(getPaths(lookupAddress(dstAddr.getHostString()), dstAddr.getPort()));
+ }
+
+ /**
+ * Request paths from the local ISD/AS to the destination.
+ *
+ * @param dstAddress Destination SCION address
+ * @return All paths returned by the path service.
+ */
+ public List getPaths(ScionAddress dstAddress, int dstPort) {
+ long srcIsdAs = getLocalIsdAs();
+ List paths = getPathList(srcIsdAs, dstAddress.getIsdAs());
List scionPaths = new ArrayList<>(paths.size());
for (int i = 0; i < paths.size(); i++) {
- scionPaths.add(RequestPath.create(paths.get(i), dstIsdAs, dstAddress, dstPort));
+ scionPaths.add(
+ RequestPath.create(
+ paths.get(i), dstAddress.getIsdAs(), dstAddress.getInetAddress(), dstPort));
}
return scionPaths;
}
@@ -377,7 +402,7 @@ public long getLocalIsdAs() {
/**
* @param hostName hostName of the host to resolve
- * @return A ScionAddress
+ * @return The ISD/AS code for a hostname
* @throws ScionException if the DNS/TXT lookup did not return a (valid) SCION address.
*/
public long getIsdAs(String hostName) throws ScionException {
@@ -416,12 +441,20 @@ public long getIsdAs(String hostName) throws ScionException {
throw new ScionException("No DNS TXT entry \"scion\" found for host: " + hostName);
}
+ @Deprecated // Please use lookupScionAddress() instead.
+ public ScionAddress getScionAddress(String hostName) throws ScionException {
+ return lookupAddress(hostName);
+ }
+
/**
+ * Uses DNS and hostfiles to look up a SCION enabled IP address for a give host string.
+ *
* @param hostName hostName of the host to resolve
* @return A ScionAddress
* @throws ScionException if the DNS/TXT lookup did not return a (valid) SCION address.
*/
- public ScionAddress getScionAddress(String hostName) throws ScionException {
+ @Deprecated // TODO NOW: remove this lookupAddress() method in favor of lookupSocketAddress()
+ ScionAddress lookupAddress(String hostName) throws ScionException {
ScionAddress scionAddress = scionAddressCache.get(hostName);
if (scionAddress != null) {
return scionAddress;
@@ -555,4 +588,21 @@ InetAddress getExternalIP(InetSocketAddress firstHopAddress) {
}
}
}
+
+ /**
+ * @param path RequestPath that may need refreshing
+ * @param pathPolicy PathPolicy for selecting a new path when required
+ * @param expiryMargin Expiry margin, i.e. a path is considered "expired" if expiry is less than
+ * expiryMargin seconds away.
+ * @return `true` if the path was updated, otherwise `false`.
+ */
+ boolean refreshPath(RequestPath path, PathPolicy pathPolicy, int expiryMargin) {
+ if (Instant.now().getEpochSecond() + expiryMargin <= path.getDetails().getExpiration()) {
+ return false;
+ }
+ // expired, get new path
+ RequestPath newPath = pathPolicy.filter(getPaths(path));
+ ((RequestPathImpl) path).setDetails(newPath.getDetails());
+ return true;
+ }
}
diff --git a/src/main/java/org/scion/jpan/ScionUtil.java b/src/main/java/org/scion/jpan/ScionUtil.java
index 7d65f42f..637306a5 100644
--- a/src/main/java/org/scion/jpan/ScionUtil.java
+++ b/src/main/java/org/scion/jpan/ScionUtil.java
@@ -132,14 +132,15 @@ public static String toStringPath(byte[] raw) {
* @return ISD/AS codes and border outer interface IDs along the path.
*/
public static String toStringPath(RequestPath path) {
- if (path.getInterfacesList().isEmpty()) {
+ PathDetails details = path.getDetails();
+ if (details.getInterfacesList().isEmpty()) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
- int nInterfcaces = path.getInterfacesList().size();
+ int nInterfcaces = details.getInterfacesList().size();
for (int i = 0; i < nInterfcaces; i++) {
- RequestPath.PathInterface pIf = path.getInterfacesList().get(i);
+ PathDetails.PathInterface pIf = details.getInterfacesList().get(i);
if (i % 2 == 0) {
sb.append(ScionUtil.toStringIA(pIf.getIsdAs())).append(" ");
sb.append(pIf.getId()).append(">");
@@ -147,7 +148,7 @@ public static String toStringPath(RequestPath path) {
sb.append(pIf.getId()).append(" ");
}
}
- sb.append(ScionUtil.toStringIA(path.getInterfacesList().get(nInterfcaces - 1).getIsdAs()));
+ sb.append(ScionUtil.toStringIA(details.getInterfacesList().get(nInterfcaces - 1).getIsdAs()));
sb.append("]");
return sb.toString();
}
diff --git a/src/main/java/org/scion/jpan/ScmpChannel.java b/src/main/java/org/scion/jpan/ScmpChannel.java
index 458c63fa..b03d44fa 100644
--- a/src/main/java/org/scion/jpan/ScmpChannel.java
+++ b/src/main/java/org/scion/jpan/ScmpChannel.java
@@ -229,7 +229,7 @@ Scmp.TimedMessage sendEchoRequest(Scmp.EchoMessage request) throws IOException {
buffer, Scmp.Type.INFO_128, localPort, request.getSequenceNumber(), request.getData());
buffer.flip();
request.setSizeSent(buffer.remaining());
- sendRaw(buffer, path.getFirstHopAddress());
+ sendRaw(buffer, path);
int sizeReceived = receive(request);
request.setSizeReceived(sizeReceived);
@@ -261,7 +261,7 @@ Scmp.TimedMessage sendTracerouteRequest(
int posPath = ScionHeaderParser.extractPathHeaderPosition(buffer);
buffer.put(posPath + node.posHopFlags, node.hopFlags);
- sendRaw(buffer, path.getFirstHopAddress());
+ sendRaw(buffer, path);
receive(request);
return request;
@@ -356,7 +356,7 @@ void sendEchoResponses() throws IOException {
buffer, Scmp.Type.INFO_129, port, msg.getSequenceNumber(), msg.getData());
buffer.flip();
msg.setSizeSent(buffer.remaining());
- sendRaw(buffer, path.getFirstHopAddress());
+ sendRaw(buffer, path);
log.info("Responded to SCMP {} from {}", type, path.getRemoteAddress());
} else {
log.info("Dropped SCMP message with type {} from {}", type, path.getRemoteAddress());
@@ -398,7 +398,9 @@ public void close() throws IOException {
*
* @return the current Path
* @see ScionDatagramChannel#getConnectionPath()
+ * @deprecated Please use getRemoteAddress() instead
*/
+ @Deprecated
public RequestPath getConnectionPath() {
return (RequestPath) channel.getConnectionPath();
}
diff --git a/src/test/java/org/scion/jpan/api/DatagramChannelApiServerTest.java b/src/test/java/org/scion/jpan/api/DatagramChannelApiServerTest.java
index 50d00e1e..8d0f0e53 100644
--- a/src/test/java/org/scion/jpan/api/DatagramChannelApiServerTest.java
+++ b/src/test/java/org/scion/jpan/api/DatagramChannelApiServerTest.java
@@ -85,9 +85,8 @@ void send_withoutService() throws IOException {
new byte[4],
1,
new InetSocketAddress("127.0.0.1", 1));
- Path p2 = channel.send(ByteBuffer.allocate(0), path);
+ channel.send(ByteBuffer.allocate(0), path);
assertNull(channel.getService());
- assertEquals(path, p2);
}
}
diff --git a/src/test/java/org/scion/jpan/api/DatagramChannelApiTest.java b/src/test/java/org/scion/jpan/api/DatagramChannelApiTest.java
index e174854e..4893f6e4 100644
--- a/src/test/java/org/scion/jpan/api/DatagramChannelApiTest.java
+++ b/src/test/java/org/scion/jpan/api/DatagramChannelApiTest.java
@@ -133,7 +133,7 @@ void getLocalAddress_withSendResponsePath() throws IOException {
InetSocketAddress local = channel.getLocalAddress();
assertTrue(local.getAddress().isAnyLocalAddress());
// double check that we used a responsePath
- assertNull(channel.getConnectionPath());
+ assertNull(channel.getRemoteAddress());
}
}
@@ -402,6 +402,17 @@ void getPathPolicy() throws IOException {
}
}
+ @Test
+ void send_bufferSize() throws IOException {
+ try (ScionDatagramChannel channel = ScionDatagramChannel.open()) {
+ int size0 = channel.send(ByteBuffer.allocate(0), ExamplePacket.PATH);
+ assertEquals(0, size0);
+
+ int size100 = channel.send(ByteBuffer.wrap(new byte[100]), ExamplePacket.PATH);
+ assertEquals(100, size100);
+ }
+ }
+
@Test
void send_bufferTooLarge() {
RequestPath addr = ExamplePacket.PATH;
@@ -469,13 +480,16 @@ void write_ChannelClosedFails() throws IOException {
void send_disconnected_expiredRequestPath() throws IOException {
// Expected behavior: expired paths should be replaced transparently.
testExpired(
- (channel, expiredPath) -> {
+ (channel, expiringPath) -> {
ByteBuffer sendBuf = ByteBuffer.wrap(PingPongChannelHelper.MSG.getBytes());
try {
- RequestPath newPath = (RequestPath) channel.send(sendBuf, expiredPath);
- assertTrue(newPath.getExpiration() > expiredPath.getExpiration());
- assertTrue(Instant.now().getEpochSecond() < newPath.getExpiration());
- assertNull(channel.getConnectionPath());
+ long oldExpiration = expiringPath.getDetails().getExpiration();
+ assertTrue(Instant.now().getEpochSecond() > oldExpiration);
+ channel.send(sendBuf, expiringPath);
+ long newExpiration = expiringPath.getDetails().getExpiration();
+ assertTrue(newExpiration > oldExpiration);
+ assertTrue(Instant.now().getEpochSecond() < newExpiration);
+ assertNull(channel.getRemoteAddress());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -487,13 +501,16 @@ void send_disconnected_expiredRequestPath() throws IOException {
void send_connected_expiredRequestPath() throws IOException {
// Expected behavior: expired paths should be replaced transparently.
testExpired(
- (channel, expiredPath) -> {
+ (channel, expiringPath) -> {
ByteBuffer sendBuf = ByteBuffer.wrap(PingPongChannelHelper.MSG.getBytes());
try {
- RequestPath newPath = (RequestPath) channel.send(sendBuf, expiredPath);
- assertTrue(newPath.getExpiration() > expiredPath.getExpiration());
- assertTrue(Instant.now().getEpochSecond() < newPath.getExpiration());
- assertEquals(newPath, channel.getConnectionPath());
+ long oldExpiration = expiringPath.getDetails().getExpiration();
+ assertTrue(Instant.now().getEpochSecond() > oldExpiration);
+ channel.send(sendBuf, expiringPath);
+ long newExpiration = expiringPath.getDetails().getExpiration();
+ assertTrue(newExpiration > oldExpiration);
+ assertTrue(Instant.now().getEpochSecond() < newExpiration);
+ assertEquals(expiringPath, channel.getRemoteAddress());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -509,9 +526,10 @@ void write_expiredRequestPath() throws IOException {
ByteBuffer sendBuf = ByteBuffer.wrap(PingPongChannelHelper.MSG.getBytes());
try {
channel.write(sendBuf);
- RequestPath newPath = (RequestPath) channel.getConnectionPath();
- assertTrue(newPath.getExpiration() > expiredPath.getExpiration());
- assertTrue(Instant.now().getEpochSecond() < newPath.getExpiration());
+ RequestPath newPath = channel.getConnectionPath();
+ assertTrue(
+ newPath.getDetails().getExpiration() > expiredPath.getDetails().getExpiration());
+ assertTrue(Instant.now().getEpochSecond() < newPath.getDetails().getExpiration());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -552,7 +570,7 @@ private RequestPath createExpiredPath(Path basePath) throws UnknownHostException
basePath.getRemoteAddress(),
basePath.getRemotePort(),
basePath.getFirstHopAddress());
- assertTrue(Instant.now().getEpochSecond() > expiredPath.getExpiration());
+ assertTrue(Instant.now().getEpochSecond() > expiredPath.getDetails().getExpiration());
return expiredPath;
}
@@ -561,22 +579,22 @@ void getConnectionPath() throws IOException {
RequestPath addr = ExamplePacket.PATH;
ByteBuffer buffer = ByteBuffer.allocate(50);
try (ScionDatagramChannel channel = ScionDatagramChannel.open()) {
- assertNull(channel.getConnectionPath());
+ assertNull(channel.getRemoteAddress());
// send should NOT set a path
channel.send(buffer, addr);
- assertNull(channel.getConnectionPath());
+ assertNull(channel.getRemoteAddress());
// connect should set a path
channel.connect(addr);
- assertNotNull(channel.getConnectionPath());
+ assertNotNull(channel.getRemoteAddress());
channel.disconnect();
- assertNull(channel.getConnectionPath());
+ assertNull(channel.getRemoteAddress());
// send should NOT set a path
if (Util.getJavaMajorVersion() >= 14) {
// This fails because of disconnect(), see https://bugs.openjdk.org/browse/JDK-8231880
channel.send(buffer, addr);
- assertNull(channel.getConnectionPath());
+ assertNull(channel.getRemoteAddress());
}
}
}
diff --git a/src/test/java/org/scion/jpan/api/DatagramChannelErrorHandlingTest.java b/src/test/java/org/scion/jpan/api/DatagramChannelErrorHandlingTest.java
index 3e2f8505..4c606407 100644
--- a/src/test/java/org/scion/jpan/api/DatagramChannelErrorHandlingTest.java
+++ b/src/test/java/org/scion/jpan/api/DatagramChannelErrorHandlingTest.java
@@ -58,12 +58,12 @@ void testErrorHandling() throws IOException {
RequestPath path1 = paths.get(0);
channel.connect(path0);
channel.write(ByteBuffer.allocate(0));
- assertEquals(path0, channel.getConnectionPath());
+ assertEquals(path0, channel.getRemoteAddress());
// TODO Use mock instead of daemon?
MockNetwork.returnScmpErrorOnNextPacket(Scmp.TypeCode.TYPE_5);
channel.write(ByteBuffer.allocate(0));
- assertEquals(path0, channel.getConnectionPath());
+ assertEquals(path0, channel.getRemoteAddress());
assertEquals(1, scmpReceived.get());
// mock.setSendCallback((byteBuffer,path) -> {});
diff --git a/src/test/java/org/scion/jpan/api/DatagramSocketApiTest.java b/src/test/java/org/scion/jpan/api/DatagramSocketApiTest.java
index f4356497..11661e0d 100644
--- a/src/test/java/org/scion/jpan/api/DatagramSocketApiTest.java
+++ b/src/test/java/org/scion/jpan/api/DatagramSocketApiTest.java
@@ -618,8 +618,9 @@ void send_connected_expiredRequestPath() throws IOException {
try {
socket.send(packet);
RequestPath newPath = socket.getConnectionPath();
- assertTrue(newPath.getExpiration() > expiredPath.getExpiration());
- assertTrue(Instant.now().getEpochSecond() < newPath.getExpiration());
+ assertTrue(
+ newPath.getDetails().getExpiration() > expiredPath.getDetails().getExpiration());
+ assertTrue(Instant.now().getEpochSecond() < newPath.getDetails().getExpiration());
// assertNull(channel.getCurrentPath());
} catch (IOException e) {
throw new RuntimeException(e);
@@ -660,7 +661,7 @@ private RequestPath createExpiredPath(Path basePath) throws UnknownHostException
basePath.getRemoteAddress(),
basePath.getRemotePort(),
basePath.getFirstHopAddress());
- assertTrue(Instant.now().getEpochSecond() > expiredPath.getExpiration());
+ assertTrue(Instant.now().getEpochSecond() > expiredPath.getDetails().getExpiration());
return expiredPath;
}
diff --git a/src/test/java/org/scion/jpan/api/ScionServiceTest.java b/src/test/java/org/scion/jpan/api/ScionServiceTest.java
index 011127ab..1c7cb0eb 100644
--- a/src/test/java/org/scion/jpan/api/ScionServiceTest.java
+++ b/src/test/java/org/scion/jpan/api/ScionServiceTest.java
@@ -125,8 +125,8 @@ void getPath() throws IOException {
assertEquals(dstIA, path.getRemoteIsdAs());
assertEquals(36, path.getRawPath().length);
- assertEquals("127.0.0.10:31004", path.getInterface().getAddress());
- assertEquals(2, path.getInterfacesList().size());
+ assertEquals("127.0.0.10:31004", path.getDetails().getInterface().getAddress());
+ assertEquals(2, path.getDetails().getInterfacesList().size());
// assertEquals(1, viewer.getInternalHopsList().size());
// assertEquals(0, viewer.getMtu());
// assertEquals(0, viewer.getLinkTypeList().size());
diff --git a/src/test/java/org/scion/jpan/demo/PingPongChannelClient.java b/src/test/java/org/scion/jpan/demo/PingPongChannelClient.java
index 765986ef..904dde08 100644
--- a/src/test/java/org/scion/jpan/demo/PingPongChannelClient.java
+++ b/src/test/java/org/scion/jpan/demo/PingPongChannelClient.java
@@ -53,7 +53,7 @@ private static void run() throws IOException {
channel.write(sendBuf);
println(
"Sent via "
- + ScionUtil.toStringPath((RequestPath) channel.getConnectionPath())
+ + ScionUtil.toStringPath((RequestPath) channel.getRemoteAddress())
+ ": "
+ msg);
diff --git a/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java b/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java
index b85d2dce..b79ccf58 100644
--- a/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java
+++ b/src/test/java/org/scion/jpan/demo/ScmpEchoDemo.java
@@ -149,8 +149,8 @@ private void printPath(RequestPath path) {
// ").append(channel.getLocalAddress().getAddress().getHostAddress()).append(nl);
sb.append("Using path:").append(nl);
sb.append(" Hops: ").append(ScionUtil.toStringPath(path));
- sb.append(" MTU: ").append(path.getMtu());
- sb.append(" NextHop: ").append(path.getInterface().getAddress()).append(nl);
+ sb.append(" MTU: ").append(path.getDetails().getMtu());
+ sb.append(" NextHop: ").append(path.getDetails().getInterface().getAddress()).append(nl);
println(sb.toString());
}
diff --git a/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java b/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java
index db8d230d..911f489f 100644
--- a/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java
+++ b/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java
@@ -129,8 +129,8 @@ private void printPath(RequestPath path) {
// sb.append(" ").append(channel.getLocalAddress().getAddress().getHostAddress()).append(nl);
sb.append("Using path:").append(nl);
sb.append(" Hops: ").append(ScionUtil.toStringPath(path));
- sb.append(" MTU: ").append(path.getMtu());
- sb.append(" NextHop: ").append(path.getInterface().getAddress()).append(nl);
+ sb.append(" MTU: ").append(path.getDetails().getMtu());
+ sb.append(" NextHop: ").append(path.getDetails().getInterface().getAddress()).append(nl);
println(sb.toString());
}
diff --git a/src/test/java/org/scion/jpan/demo/ShowpathsDemo.java b/src/test/java/org/scion/jpan/demo/ShowpathsDemo.java
index 9510b54c..3ad8ccbc 100644
--- a/src/test/java/org/scion/jpan/demo/ShowpathsDemo.java
+++ b/src/test/java/org/scion/jpan/demo/ShowpathsDemo.java
@@ -114,9 +114,9 @@ private void runDemo(long destinationIA) throws IOException {
+ "] Hops: "
+ ScionUtil.toStringPath(path)
+ " MTU: "
- + path.getMtu()
+ + path.getDetails().getMtu()
+ " NextHop: "
- + path.getInterface().getAddress()
+ + path.getDetails().getInterface().getAddress()
+ " LocalIP: "
+ localIP;
println(sb);
diff --git a/src/test/java/org/scion/jpan/testutil/PingPongHelperBase.java b/src/test/java/org/scion/jpan/testutil/PingPongHelperBase.java
index 73448d32..1b7b6255 100644
--- a/src/test/java/org/scion/jpan/testutil/PingPongHelperBase.java
+++ b/src/test/java/org/scion/jpan/testutil/PingPongHelperBase.java
@@ -107,11 +107,11 @@ void runPingPong(ServerFactory serverFactory, ClientFactory clientFactory, boole
}
InetSocketAddress serverAddress = servers[0].getLocalAddress();
MockDNS.install(MockNetwork.TINY_SRV_ISD_AS, serverAddress.getAddress());
- RequestPath scionAddress = Scion.defaultService().getPaths(serverAddress).get(0);
+ RequestPath requestPath = Scion.defaultService().lookupAndGetPath(serverAddress, null);
Thread[] clients = new Thread[nClients];
for (int i = 0; i < clients.length; i++) {
- clients[i] = clientFactory.create(i, scionAddress, nRounds);
+ clients[i] = clientFactory.create(i, requestPath, nRounds);
clients[i].setName("Client-thread-" + i);
clients[i].start();
}