Skip to content

Commit

Permalink
Override external/source address (#58)
Browse files Browse the repository at this point in the history
* override source address

---------

Co-authored-by: Tilmann Zäschke <tilmann.zaeschke@inf.ethz.ch>
  • Loading branch information
tzaeschke and Tilmann Zäschke authored Apr 29, 2024
1 parent 1e8ec06 commit 2a0bca4
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Channel demo cleanup. [#52](https://github.com/netsec-ethz/scion-java-client/pull/52)
- Address/ISD/AS caching. [#54](https://github.com/netsec-ethz/scion-java-client/pull/54)
- `DatagramSocket` [#31](https://github.com/netsec-ethz/scion-java-client/pull/31)
- `setOverrideSourceAddress()` [#58](https://github.com/netsec-ethz/scion-java-client/pull/58)

### Changed
- BREAKING CHANGE: Changed maven artifactId to "client"
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The following artifact contains the complete SCION Java implementation:
- TCP
- Many more

### WARNING
### WARNING - Dispatcher
JPAN connects directly to SCION **without dispatcher**.

Currently (April 2024), the SCION system uses a "dispatcher" (a process that runs on endhosts,
Expand All @@ -49,6 +49,17 @@ JPAN can be used in one of the following ways:
- When you need to run a local system with dispatcher, you can try to use port forwarding
to forward incoming data to your Java application port. The application port must not be 30041.

### WARNING - NAT
JPAN does not work well when using a local NAT.
The problem is that the SCION header must contain the external IP address (i.e. the IP visible to
first border router) of the end host. When using a NAT, this needs to be the external IP of the NAT.

JPAN cannot currently auto-detect this IP.
To work with a NAT, please use `setOverrideSourceAddress(externalAddress)` to force JPAN to use
the specified external address instead of the eternal IP of the end-host.

Note that this solution only works for NATs, there is currently no solution for proxies.

## API

The central classes of the API are:
Expand Down Expand Up @@ -335,6 +346,9 @@ The certificates can be renewed by recreating the network with
This error occurs when requesting a path with an ISD/AS code that is not
known in the network.

### Response packets cannot get past a local NAT or PROXY
Solving this requires some additional configuration, see `setOverrideSourceAddress` above.

### IllegalThreadStateException
```
[WARNING] thread Thread[grpc-default-worker-ELG-1-1,5,com.app.SimpleScmp] was interrupted but is still alive after waiting at least 15000msecs
Expand Down
54 changes: 38 additions & 16 deletions src/main/java/org/scion/jpan/AbstractDatagramChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ abstract class AbstractDatagramChannel<C extends AbstractDatagramChannel<?>> imp
private int cfgTrafficClass;
private Consumer<Scmp.Message> errorListener;
private boolean cfgRemoteDispatcher = false;
private InetSocketAddress overrideExternalAddress = null;

protected AbstractDatagramChannel(ScionService service) throws IOException {
this(service, DatagramChannel.open());
Expand Down Expand Up @@ -382,6 +383,20 @@ public void configureRemoteDispatcher(boolean hasDispatcher) {
this.cfgRemoteDispatcher = hasDispatcher;
}

/**
* This allows overriding the source address in SCION headers. This can be useful when a host is
* located behind a NAT. The specified source address should in this case be the external address
* of the NAT.
*
* @deprecated This is deprecated because it is considered a workaround and will likely be removed
* in a future version.
* @param address The external source address
*/
@Deprecated
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) {
Expand Down Expand Up @@ -553,23 +568,30 @@ protected void buildHeader(
srcPort = rPath.getLocalPort();
} else {
srcIA = getOrCreateService().getLocalIsdAs();
// Get external host address. This must be done *after* refreshing the path!
if (localAddress.isAnyLocalAddress()) {
// For sending request path we need to have a valid local external address.
// If the local address is a wildcard address then we get the external IP
// elsewhere (from the service).

// TODO cache this or add it to path object?
srcAddress = getOrCreateService().getExternalIP(path.getFirstHopAddress());
if (overrideExternalAddress != null) {
// Use specified external address. This can be useful to work with NATs.
srcAddress = overrideExternalAddress.getAddress();
srcPort = overrideExternalAddress.getPort();
} else {
srcAddress = localAddress;
}
srcPort = ((InetSocketAddress) channel.getLocalAddress()).getPort();
if (srcPort == 0) {
// This has apparently been fixed in Java 14: https://bugs.openjdk.org/browse/JDK-8231880
throw new IllegalStateException(
"Local port is 0. This happens after calling "
+ "disconnect(). Please connect() or bind() before send() or write().");
// Get external host address. This must be done *after* refreshing the path!
if (localAddress.isAnyLocalAddress()) {
// For sending request path we need to have a valid local external address.
// If the local address is a wildcard address then we get the external IP
// elsewhere (from the service).

// TODO cache this or add it to path object?
srcAddress = getOrCreateService().getExternalIP(path.getFirstHopAddress());
} else {
srcAddress = localAddress;
}
srcPort = ((InetSocketAddress) channel.getLocalAddress()).getPort();
if (srcPort == 0) {
// This has apparently been fixed in Java 14:
// https://bugs.openjdk.org/browse/JDK-8231880
throw new IllegalStateException(
"Local port is 0. This happens after calling "
+ "disconnect(). Please connect() or bind() before send() or write().");
}
}
}

Expand Down
37 changes: 37 additions & 0 deletions src/main/java/org/scion/jpan/ScmpChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ public void close() throws IOException {
}
}

/**
* Get the currently connected path. The connected path is set during channel creation.
*
* @return the current Path
* @see org.scion.jpan.DatagramChannel#getConnectionPath()
*/
public RequestPath getConnectionPath() {
return (RequestPath) channel.getConnectionPath();
}
Expand All @@ -255,4 +261,35 @@ public InetSocketAddress getLocalAddress() throws IOException {
public InetSocketAddress getRemoteAddress() throws IOException {
return channel.getRemoteAddress();
}

public synchronized PathPolicy getPathPolicy() {
return channel.getPathPolicy();
}

/**
* Set the path policy. The default path policy is set in {@link PathPolicy#DEFAULT}. If the
* socket is connected, this method will request a new path using the new policy.
*
* <p>After initially setting the path policy, it is used to request a new path during write() and
* send() whenever a path turns out to be close to expiration.
*
* @param pathPolicy the new path policy
* @see PathPolicy#DEFAULT
* @see org.scion.jpan.DatagramChannel#setPathPolicy(PathPolicy)
*/
public synchronized void setPathPolicy(PathPolicy pathPolicy) throws IOException {
channel.setPathPolicy(pathPolicy);
}

/**
* Specify an source address override. See {@link
* org.scion.jpan.DatagramChannel#setOverrideSourceAddress(InetSocketAddress)}.
*
* @param overrideSourceAddress Override address
* @deprecated This will likely be removed in a future version
*/
@Deprecated
public synchronized void setOverrideSourceAddress(InetSocketAddress overrideSourceAddress) {
channel.setOverrideSourceAddress(overrideSourceAddress);
}
}
14 changes: 13 additions & 1 deletion src/main/java/org/scion/jpan/socket/DatagramSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public DatagramSocket(SocketAddress bindAddress) throws SocketException {
this(bindAddress, null);
}

// "private" to avoid ambiguity with DatagramSocket((SockeAddress) null) -> use create()
// "private" to avoid ambiguity with DatagramSocket((SocketAddress) null) -> use create()
private DatagramSocket(ScionService service) throws SocketException {
super(new DummyDatagramSocketImpl());
try {
Expand Down Expand Up @@ -632,6 +632,18 @@ public synchronized DatagramSocket setRemoteDispatcher(boolean hasDispatcher) {
return this;
}

/**
* Specify an source address override. See {@link
* org.scion.jpan.DatagramChannel#setOverrideSourceAddress(InetSocketAddress)}.
*
* @param overrideSourceAddress Override address
* @deprecated This will likely be removed in a future version
*/
@Deprecated
public synchronized void setOverrideSourceAddress(InetSocketAddress overrideSourceAddress) {
channel.setOverrideSourceAddress(overrideSourceAddress);
}

private static class DummyDatagramSocketImpl extends DatagramSocketImpl {

@Override
Expand Down
43 changes: 43 additions & 0 deletions src/test/java/org/scion/jpan/api/DatagramChannelApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,47 @@ void setOption_Standard() throws IOException {
() -> channel.setOption(StandardSocketOptions.SO_RCVBUF, 10000));
}
}

@Test
void setOverrideSourceAddress() throws IOException {
ByteBuffer buf = ByteBuffer.wrap("Hello".getBytes());
InetAddress overrideSrcIP = InetAddress.getByAddress(new byte[] {42, 42, 42, 42});
int overrideSrcPort = 4242;
InetSocketAddress overrideSrc = new InetSocketAddress(overrideSrcIP, overrideSrcPort);
try (MockDatagramChannel mock = MockDatagramChannel.open();
DatagramChannel channel = DatagramChannel.open(Scion.defaultService(), mock)) {

// initialize local address
mock.setSendCallback((buffer, address) -> 0);
channel.send(buf, dummyAddress);

// src should be 127.0.0.1
int localPort = channel.getLocalAddress().getPort();
InetAddress localIP = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
InetSocketAddress localAddress = new InetSocketAddress(localIP, localPort);
mock.setSendCallback((buffer, address) -> checkAddress(buffer, localAddress));
channel.send(buf, dummyAddress);

// src should be overrideAddress
channel.setOverrideSourceAddress(overrideSrc);
mock.setSendCallback((buffer, address) -> checkAddress(buffer, overrideSrc));
channel.send(buf, dummyAddress);

// src should be local address again
channel.setOverrideSourceAddress(null);
mock.setSendCallback((buffer, address) -> checkAddress(buffer, localAddress));
channel.send(buf, dummyAddress);
}
}

private int checkAddress(ByteBuffer buffer, InetSocketAddress expectedAddress) {
ScionPacketInspector spi = ScionPacketInspector.readPacket(buffer);
try {
assertEquals(expectedAddress.getAddress(), spi.getScionHeader().getSrcHostAddress());
} catch (IOException e) {
throw new RuntimeException(e);
}
assertEquals(expectedAddress.getPort(), spi.getOverlayHeaderUdp().getSrcPort());
return 0;
}
}

0 comments on commit 2a0bca4

Please sign in to comment.