Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Dispatcher port range support #130

Merged
merged 8 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]

### TODO for 0.4.0
- AbstractDataChannel
- Remove receive(expectedHeaderType) -> just check for SCMP, otherwise -> default
- Move buildHeader() -> UDP-part out of this function.
- replace Selector.open() with channel.provider().openSelector();
- USe topo port range for connections in local AS!
- Fix demos to return an "int" that can be tested!
- ShimTest: TODO the serverService(null) should be removed once SHIM becomes the default
- Bootstrap JPAN with reverse lookup to get search-domain --> Francois

- Fix showpaths to show "127.0.0.81:31038" i.o. "/192.168.53.20"
- Bootstrap to find local search domain with reverse lookup, ask Francois!
- Cache local address, see AbstractChannel:600
srcAddress = getOrCreateService().getExternalIP(path.getFirstHopAddress());
- MockDaemon: read properties from topofile, see TODO
- Cache paths
- Server should get firstHop from topofile or daemon, not from packet IP!
-> BRs may use different ports for in/outgoing traffic.
-> Thios would also solve the server/SHIM problem, see TODO.md
- Fix @Disabled tests
- Support topofile port range
- Upgrade all JUnit topo files to post 0.11 with new format (including port range)
Expand All @@ -26,10 +39,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Inherit DatagramChannel
- Consider using https://github.com/ascopes/protobuf-maven-plugin (more up to date)

**BREAKING CHANGE**
- The SHIM now occupies port 30041. This means any application trying to use that port will fail.
- Solution #1: Just use any other port instead, the SHIM will forward traffic to it.
- Solution #2: Disable the SHIM with

### Added
- Add a SHIM, required for #130 (topo file port range support).
[#130](https://github.com/scionproto-contrib/jpan/pull/130)
[#131](https://github.com/scionproto-contrib/jpan/pull/131)
- ManagedThread test helper. [#136](https://github.com/scionproto-contrib/jpan/pull/136)
- Support for `dispatched_ports` in topo files. Deprecated `configureRemoteDispatcher()`.
[#130](https://github.com/scionproto-contrib/jpan/pull/130)

### Changed
- Buildified PingPong test helper. [#132](https://github.com/scionproto-contrib/jpan/pull/132)
Expand Down
55 changes: 37 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ JPAN can be used in one of the following ways:
- You can use JPAN stand-alone (without local SCION installation),
however it must listen on port 30041 for incoming SCION packets because
SCION routers currently will forward data only to that port.
- If you are contacting an endhost within your own AS, and the endhost uses a dispatcher, then you
- ~~If you are contacting an endhost within your own AS, and the endhost uses a dispatcher, then you
must set the flag `ScionDatagramChannel.configureRemoteDispatcher(true)`. This ensure that the
outgoing packet is sent to port 30041 on the remote machine. The flag has no effect on traffic
sent to a remote AS.
sent to a remote AS.~~
JPAN uses the topo file's port range to detect which parts need to be mapped to 30041.
- If you need a local SCION installation on your machine (Go implementation),
consider using the dispatch-off branch/PR.
- When you need to run a local system with dispatcher, you can try to use port forwarding
Expand Down Expand Up @@ -267,18 +268,18 @@ The following standard options are **not** supported:

`DatagramSocket` work similar to `DatagramChannel` in terms of using `Path` or `Service`.
`DatagramSocket` is somewhat discouraged because it requires storing/caching of paths internally
which can lead to increased memory usage of even failure to resolve paths, especially when handling
which can lead to increased memory usage or even failure to resolve paths, especially when handling
multiple connections over a single socket.

The problem is that `DatagramPacket` and `InetAddress` are not extensible to store path information.
For a server to be able to send data back to a client, it has to remember these paths internally.
This is done internally in a path cache that stores the received path for every remote IP address.
The cache is by default limited to 100 entries (`setPathCacheCapacity()`). In cse there are more
The cache is by default limited to 100 entries (`setPathCacheCapacity()`). In case there are more
than 100 remote clients, the cache will 'forget' those paths that haven't been used for the longest
time. That means the server won't be able to send anything anymore to these forgotten clients.

This can become a security problem if an attacker initiates connections from many different (or
spoofed) IPs, causing the cache to consume a lot of memory or to overflow, being unable to
spoofed) IPs, causing the cache to consume a lot of memory or to overflow, becoming unable to
answer to valid requests.

Internally, the `DatagramSocket` uses a SCION `DatagraChannel`.
Expand All @@ -297,16 +298,16 @@ API beyond the standard Java `DatagramScoket`:

## Performance pitfalls

- **Using `SocketAddress` for `send()`**. `send(buffer, socketAddress)` is a convenience function. However, when sending
multiple packets to the same destination, one should use `path = send(buffer, path)` or `connect()` + `write()` in
order to avoid frequent path lookups.
- **Using `SocketAddress` for `send()`**. `send(buffer, socketAddress)` is a convenience function.
However, when sending multiple packets to the same destination, one should use
`path = send(buffer, path)` or `connect()` + `write()` in order to avoid frequent path lookups.

- **Using expired path (client).** When using `send(buffer, path)` with an expired `Path`, the channel will
transparently look up a new path. This works but causes a path lookup for every `send()`.
Solution: always use the latest path returned by send, e.g. `path = send(buffer, path)`.
- **Using expired path (client).** When using `send(buffer, path)` with an expired `Path`, the
channel will transparently look up a new path. This works but causes a path lookup for every
`send()`. Solution: always use the latest path returned by send, e.g. `path = send(buffer, path)`.

- **Using expired path (server).** When using `send(buffer, path)` with an expired `Path`, the channel will
simple send it anyway.
- **Using expired path (server).** When using `send(buffer, path)` with an expired `Path`, the
channel will simple send it anyway.

## Configuration

Expand Down Expand Up @@ -342,17 +343,35 @@ while the other options are skipped if no property or environment variable is de


### DNS

JPAN will check the OS default DNS server to resolve SCION addresses.
In addition, addresses can be specified in a `/etc/scion/hosts` file. The location of the hosts file
is configurable, see next section.

### SHIM

Every JPAN application will try to start a
[SHIM dispatcher](https://docs.scion.org/en/latest/dev/design/router-port-dispatch.html)
on port 30041. The SHIM is required to support the `dispathed_ports` feature in topo files.

A SHIM does no traffic checking, it blindly forwards every parseable packet to the inscribed SCION
destination address. That means a JPAN SHIM will act as a SHIM for all applications on a machine.
(The slight problem being that if the application stops, the SHIM is stopped, leaving all other
applications without a SHIM).

If the SHIM cannot be started because port 30041 is taken, the application will start anyway,
assuming that another SHIM is running on 30041.

Whether a SHIM is started can be controlled with a configuration option, see below.

### Other Options

| Option | Java property | Environment variable | Default value |
|----------------------------------------------------------------------------------------------------------------------|---------------------------------------|--------------------------------------|--------------------|
| Path expiry margin. Before sending a packet a new path is requested if the path is about to expire within X seconds. | `org.scion.pathExpiryMargin` | `SCION_PATH_EXPIRY_MARGIN` | 10 |
| Location of `hosts` file. Multiple location can be specified separated by `;`. | `org.scion.hostsFiles` | `SCION_HOSTS_FILES` | `/etc/scion/hosts` |
| Minimize segment requests to local AS at the cost of reduced range of path available. | `org.scion.resolver.experimentalMinimizeRequests` | `EXPERIMENTAL_SCION_RESOLVER_MINIMIZE_REQUESTS` | `false` |
| Option | Java property | Environment variable | Default value |
|------------------------------------------------------------------------------------------------------|---------------------------------------------------|-------------------------------------------------|--------------------|
| Path expiry margin. Before sending a packet a new path is requested if the path is about to expire within X seconds. | `org.scion.pathExpiryMargin` | `SCION_PATH_EXPIRY_MARGIN` | 10 |
| Location of `hosts` file. Multiple location can be specified separated by `;`. | `org.scion.hostsFiles` | `SCION_HOSTS_FILES` | `/etc/scion/hosts` |
| Start SHIM. | `org.scion.shim` | `SCION_SHIM` | `true` |
| Minimize segment requests to local AS at the cost of reduced range of path available. | `org.scion.resolver.experimentalMinimizeRequests` | `EXPERIMENTAL_SCION_RESOLVER_MINIMIZE_REQUESTS` | `false` |

`EXPERIMENTAL_SCION_RESOLVER_MINIMIZE_REQUESTS` is a non-standard option that request CORE segments only of other
path can be constructed. This may reduce response time when requesting new paths. It is very likely,
Expand Down
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@
- Implement interfaces from nio.DatagramChannel
- Look into Selectors: https://www.baeldung.com/java-nio-selector
- Consider subclassing DatagramChannel directly.
- DISPATCHER migration:
- Daemon supposedly provides information about dispatcher. Double check updated proto files
- Parse topofiles with port range information -> indicates DISPATCHER presence
- Consider SHIM support. SHIM is a compatibility component that supports
old border-router software (requiring a fixed port on the client, unless
the client is listening on this very port). When SHIM is used, we cannot
Expand Down
39 changes: 36 additions & 3 deletions src/main/java/org/scion/jpan/AbstractDatagramChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,26 @@
}
}

private void ensureBound() throws IOException {
protected void ensureBound() throws IOException {
synchronized (stateLock) {
if (localAddress == null) {
bind(null);
LocalTopology.DispatcherPortRange ports = getService().getLocalPortRange();
if (ports.hasPortRange()) {
// This is a bit ugly, we iterate through all ports to find a free one.
int min = ports.getPortMin();
int max = ports.getPortMax();
for (int port = min; port <= max; port++) {
try {
bind(new InetSocketAddress(port));
return;
} catch (IOException e) {
// ignore and try next port
}
}
throw new IOException("No free port found in SCION port range: " + min + "-" + max);

Check warning on line 151 in src/main/java/org/scion/jpan/AbstractDatagramChannel.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/AbstractDatagramChannel.java#L151

Added line #L151 was not covered by tests
} else {
bind(null);
}
}
}
}
Expand Down Expand Up @@ -286,6 +302,14 @@
synchronized (stateLock) {
checkConnected(false);
ensureBound();
if (localAddress.isAnyLocalAddress()) {
// Do we really need this?
// - It ensures that after connect we have a proper local address for getLocalAddress(),
// this is what connect() should do.
// - It allows us to have an ANY address underneath which could help with interface
// switching.
localAddress = getService().getExternalIP(path.getFirstHopAddress());
}
updateConnection((RequestPath) path, false);
return (C) this;
}
Expand Down Expand Up @@ -336,7 +360,7 @@
}
}

private InetSocketAddress getFirstHopAddress(ByteBuffer buffer, InetSocketAddress srcAddress) {
protected InetSocketAddress getFirstHopAddress(ByteBuffer buffer, InetSocketAddress srcAddress) {
if (getService() != null) {
int oldPos = buffer.position();
int pathPos = ScionHeaderParser.extractPathHeaderPosition(buffer);
Expand Down Expand Up @@ -395,7 +419,9 @@
*
* @param hasDispatcher Set to 'true' if remote end-host uses a dispatcher and requires using port
* 30041.
* @deprecated Not required anymore, will be removed for 0.5.0
*/
@Deprecated // TODO remove for 0.5.0
public void configureRemoteDispatcher(boolean hasDispatcher) {
this.cfgRemoteDispatcher = hasDispatcher;
}
Expand All @@ -412,6 +438,13 @@
}

protected int sendRaw(ByteBuffer buffer, Path path) throws IOException {
// For inter-AS connections we need to send directly to the destination address.
// We also need to respect the port range and use 30041 when applicable.
if (getService() != null && path.getRawPath().length == 0) {
InetSocketAddress remoteHostIP = path.getFirstHopAddress();
remoteHostIP = getService().getLocalPortRange().mapToLocalPort(remoteHostIP);
return channel.send(buffer, remoteHostIP);
}
if (cfgRemoteDispatcher && path.getRawPath().length == 0) {
InetAddress remoteHostIP = path.getFirstHopAddress().getAddress();
return channel.send(buffer, new InetSocketAddress(remoteHostIP, Constants.DISPATCHER_PORT));
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/scion/jpan/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public final class Constants {

public static final String ENV_DNS_SEARCH_DOMAINS = "SCION_DNS_SEARCH_DOMAINS";

/** Run the SHIM (or not). */
public static final String PROPERTY_SHIM = "org.scion.shim";

public static final String ENV_SHIM = "SCION_SHIM";
public static final boolean DEFAULT_SHIM = true;

/**
* Non-public property that allows specifying DNS TXT entries for debugging. Example with two
* entries: server1.com="scion=1-ff00:0:110,127.0.0.1";server2.ch="scion=1-ff00:0:112,::1"
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/scion/jpan/PathPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@

private boolean checkPath(Path path) {
for (PathMetadata.PathInterface pif : path.getMetadata().getInterfacesList()) {
int isd = (int) (pif.getIsdAs() >>> 48);
int isd = ScionUtil.extractIsd(pif.getIsdAs());

Check warning on line 81 in src/main/java/org/scion/jpan/PathPolicy.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/PathPolicy.java#L81

Added line #L81 was not covered by tests
if (!allowedIsds.contains(isd)) {
return false;
}
Expand All @@ -104,7 +104,7 @@

private boolean checkPath(Path path) {
for (PathMetadata.PathInterface pif : path.getMetadata().getInterfacesList()) {
int isd = (int) (pif.getIsdAs() >>> 48);
int isd = ScionUtil.extractIsd(pif.getIsdAs());

Check warning on line 107 in src/main/java/org/scion/jpan/PathPolicy.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/PathPolicy.java#L107

Added line #L107 was not covered by tests
if (disallowedIsds.contains(isd)) {
return false;
}
Expand Down
16 changes: 12 additions & 4 deletions src/main/java/org/scion/jpan/ScionDatagramSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ public ScionDatagramSocket(SocketAddress bindAddress) throws SocketException {
}

// "private" to avoid ambiguity with DatagramSocket((SocketAddress) null) -> use create()
protected ScionDatagramSocket(ScionService service) throws SocketException {
protected ScionDatagramSocket(ScionService service, DatagramChannel channel)
throws SocketException {
super(new DummyDatagramSocketImpl());
try {
channel = new SelectingDatagramChannel(service);
this.channel = new SelectingDatagramChannel(service, channel);
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
Expand All @@ -88,7 +89,7 @@ protected ScionDatagramSocket(ScionService service) throws SocketException {
// "private" for consistency, all non-standard constructors are private -> use create()
protected ScionDatagramSocket(SocketAddress bindAddress, ScionService service)
throws SocketException {
this(service);
this(service, null);
// DatagramSockets always immediately bind unless the bindAddress is null.
if (bindAddress != null) {
try {
Expand All @@ -109,7 +110,12 @@ protected ScionDatagramSocket(SocketAddress bindAddress, ScionService service)
* @return a new socket.
*/
public static ScionDatagramSocket create(ScionService service) throws SocketException {
return new ScionDatagramSocket(service);
return new ScionDatagramSocket(service, null);
}

public static ScionDatagramSocket create(ScionService service, DatagramChannel channel)
throws SocketException {
return new ScionDatagramSocket(service, channel);
}

public static ScionDatagramSocket create(SocketAddress bindAddress, ScionService service)
Expand Down Expand Up @@ -634,7 +640,9 @@ public synchronized void setPathPolicy(PathPolicy pathPolicy) throws IOException
* @param hasDispatcher Set to 'true' if remote end-host uses a dispatcher and requires using port
* 30041.
* @see ScionDatagramChannel#configureRemoteDispatcher(boolean)
* @deprecated Not required anymore, will be removed for 0.5.0
*/
@Deprecated // TODO remove for 0.5.0
public synchronized ScionDatagramSocket setRemoteDispatcher(boolean hasDispatcher) {
channel.configureRemoteDispatcher(hasDispatcher);
return this;
Expand Down
31 changes: 23 additions & 8 deletions src/main/java/org/scion/jpan/ScionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.scion.jpan.Constants.PROPERTY_DNS_SEARCH_DOMAINS;
import static org.scion.jpan.Constants.PROPERTY_USE_OS_SEARCH_DOMAINS;

import com.google.protobuf.Empty;
import io.grpc.*;
import java.io.IOException;
import java.net.InetAddress;
Expand All @@ -39,7 +40,6 @@
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.scion.jpan.internal.*;
import org.scion.jpan.proto.control_plane.SegmentLookupServiceGrpc;
import org.scion.jpan.proto.daemon.Daemon;
Expand Down Expand Up @@ -75,6 +75,7 @@
private final ScionBootstrapper bootstrapper;
private final DaemonServiceGrpc.DaemonServiceBlockingStub daemonStub;
private final SegmentLookupServiceGrpc.SegmentLookupServiceBlockingStub segmentStub;
private LocalTopology.DispatcherPortRange portRange;

private final boolean minimizeRequests;
private final ManagedChannel channel;
Expand Down Expand Up @@ -645,14 +646,28 @@
}
}

List<String> getBorderRouterStrings() {
if (daemonStub != null) {
return getInterfaces().values().stream()
.map(i -> i.getAddress().getAddress())
.collect(Collectors.toList());
} else {
return bootstrapper.getLocalTopology().getBorderRouterAddresses();
LocalTopology.DispatcherPortRange getLocalPortRange() {
if (portRange == null) {
if (bootstrapper != null) {
portRange = bootstrapper.getLocalTopology().getPortRange();
} else if (daemonStub != null) {
// try daemon
Daemon.PortRangeResponse response;
try {
response = daemonStub.portRange(Empty.getDefaultInstance());
portRange =
LocalTopology.DispatcherPortRange.create(
response.getDispatchedPortStart(), response.getDispatchedPortEnd());
} catch (StatusRuntimeException e) {
LOG.warn("ERROR getting port range from daemon: {}", e.getMessage());

Check warning on line 662 in src/main/java/org/scion/jpan/ScionService.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/ScionService.java#L661-L662

Added lines #L661 - L662 were not covered by tests
// Daemon doesn't support port range.
portRange = LocalTopology.DispatcherPortRange.createEmpty();

Check warning on line 664 in src/main/java/org/scion/jpan/ScionService.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/ScionService.java#L664

Added line #L664 was not covered by tests
}
} else {
portRange = LocalTopology.DispatcherPortRange.createAll();

Check warning on line 667 in src/main/java/org/scion/jpan/ScionService.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/jpan/ScionService.java#L667

Added line #L667 was not covered by tests
}
}
return portRange;
}

InetSocketAddress getBorderRouterAddress(int interfaceID) {
Expand Down
Loading