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

ScionService logic #18

Merged
merged 5 commits into from
Feb 26, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
[#14](https://github.com/tzaeschke/phtree-cpp/pull/14),
[#15](https://github.com/tzaeschke/phtree-cpp/pull/15),
[#17](https://github.com/tzaeschke/phtree-cpp/pull/17)
- BREAKING CHANGE:`ScionService` instances created via `Scion.newXYZ`
will not be considered by `Scion.defaultService()`. Also, `DatagramChannel.getService()`
does not create a service if none exists.
[#18](https://github.com/tzaeschke/phtree-cpp/pull/18)

### Fixed
- Fixed: SCMP problem when pinging local AS.
Expand Down
29 changes: 29 additions & 0 deletions doc/Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ Also, when roaming, the provider may not actually support SCION. In this case th
device would need to connect to some kind of gateway to connect to either a daemon or
at least a topology server + control server.

## Server / Client

Server functionality, such as `open()` and `receive()` -> `send(RequestPath)`, should _not_ require
any access to a daemon or control service. The main reason is that it ensures efficiency
by completely avoiding any type of interaction with daemon/CS.
Interaction with daemon/CS should not be denied but it should not occur during default operations.

Client functionality will usually require a `ScionService`. If none is provided during `open()`,
a service will internally be requested via `Scion.defaultService()`.
While explicit initialization is usually preferable as a design choice, this implicit
initialization was implemented to allow usage of `open()` without additional arguments.
This allows being much closer to the native Java `DatagramChannel` API.

An alternative would be to push the the delayed/implicit initialization into the `ScionService`
class. We would always have a `ScionService` instance, but it wouldn't initially connect to
a daemon or control service. The disadvantage is that creation of a `ScionService` would have
to fail "late", i.e. we can create it but only when we use it would be know whether it
is actually able to connect to something.

## ScionService

The `ScionService` implements all interactions with a local daemon or with a control service.
When a `ScionService` is required internally (e.g. by `DatagramChannel`) and none has been
specified, a `ScionService` will be requested (and if none has been created yet, created) via
`Scion.defaultService()`.

A `ScionService` created via specific factory methods (`Scion.newServiceWithDNS`, etc) will _not_ be
used or returned by `Scion.defaultService()`.

## Library dependencies etc

- Use Maven (instead of Gradle or something else): Maven is still the most used framework and arguable the best (
Expand Down
33 changes: 15 additions & 18 deletions src/main/java/org/scion/AbstractDatagramChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,20 @@ abstract class AbstractDatagramChannel<C extends AbstractDatagramChannel<?>> imp
private Consumer<Scmp.Message> errorListener;

protected AbstractDatagramChannel(ScionService service) throws IOException {
this.channel = java.nio.channels.DatagramChannel.open();
this(service, DatagramChannel.open());
}

protected AbstractDatagramChannel(
ScionService service, java.nio.channels.DatagramChannel channel) {
this.channel = channel;
this.service = service;
}

protected synchronized void configureBlocking(boolean block) throws IOException {
channel.configureBlocking(block);
}

// `protected` because it should not be visible in ScmpChannel API.
protected synchronized boolean isBlocking() {
return channel.isBlocking();
}
Expand All @@ -77,24 +83,15 @@ public synchronized void setPathPolicy(PathPolicy pathPolicy) throws IOException
}
}

public synchronized ScionService getService() {
// Late and implicit initialization is questionable but probably the best choice here.
// 1) Under any conceivable circumstances there should only be one service instance required,
// so defaultService() should yield the 'correct' instance.
// 2) defaultService() should also be 'correct' in the sense that no other instance should ever
// be required. The dedicated newServiceXYZ() methods should be unnecessary.
//
// Why do we do this? It allows us to use DatagramChannel.open() in all situations,
// independent of whether we are a client (requires Service) or a server (should not use
// Service).
protected synchronized ScionService getOrCreateService() {
if (service == null) {
service = Scion.defaultService();
service = ScionService.defaultService();
}
return this.service;
}

public synchronized void setService(ScionService service) {
this.service = service;
public synchronized ScionService getService() {
return this.service;
}

protected DatagramChannel channel() {
Expand Down Expand Up @@ -150,7 +147,7 @@ public synchronized C connect(SocketAddress addr) throws IOException {
throw new IllegalArgumentException(
"connect() requires an InetSocketAddress or a ScionSocketAddress.");
}
return connect(pathPolicy.filter(getService().getPaths((InetSocketAddress) addr)));
return connect(pathPolicy.filter(getOrCreateService().getPaths((InetSocketAddress) addr)));
}

/**
Expand Down Expand Up @@ -185,7 +182,7 @@ public synchronized Path getCurrentPath() {
return path;
}

protected void setPath(RequestPath path) {
protected synchronized void setPath(RequestPath path) {
this.path = path;
}

Expand Down Expand Up @@ -362,7 +359,7 @@ protected void buildHeaderNoRefresh(
channel.connect(connection);
}

srcIA = getService().getLocalIsdAs();
srcIA = getOrCreateService().getLocalIsdAs();
// Get external host address. This must be done *after* refreshing the path!
InetSocketAddress srcSocketAddress = (InetSocketAddress) channel.getLocalAddress();
srcAddress = srcSocketAddress.getAddress().getAddress();
Expand Down Expand Up @@ -392,7 +389,7 @@ protected RequestPath ensureUpToDate(RequestPath path) throws IOException {

private RequestPath updatePath(RequestPath path) throws IOException {
// expired, get new path
RequestPath newPath = pathPolicy.filter(getService().getPaths(path));
RequestPath newPath = pathPolicy.filter(getOrCreateService().getPaths(path));

if (isConnected) { // equal to !isBound at this point
if (!newPath.getFirstHopAddress().equals(this.connection)) {
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/org/scion/DatagramChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,24 @@ public class DatagramChannel extends AbstractDatagramChannel<DatagramChannel>
private final ByteBuffer bufferReceive;
private final ByteBuffer bufferSend;

protected DatagramChannel() throws IOException {
this(null);
}

protected DatagramChannel(ScionService service) throws IOException {
super(service);
protected DatagramChannel(ScionService service, java.nio.channels.DatagramChannel channel)
throws IOException {
super(service, channel);
this.bufferReceive = ByteBuffer.allocateDirect(getOption(StandardSocketOptions.SO_RCVBUF));
this.bufferSend = ByteBuffer.allocateDirect(getOption(StandardSocketOptions.SO_SNDBUF));
}

public static DatagramChannel open() throws IOException {
return new DatagramChannel();
return open(null);
}

public static DatagramChannel open(ScionService service) throws IOException {
return new DatagramChannel(service);
return open(service, java.nio.channels.DatagramChannel.open());
}

public static DatagramChannel open(
ScionService service, java.nio.channels.DatagramChannel channel) throws IOException {
return new DatagramChannel(service, channel);
}

// TODO we return `void` here. If we implement SelectableChannel
Expand Down Expand Up @@ -85,7 +87,9 @@ public synchronized void send(ByteBuffer srcBuffer, SocketAddress destination)
if (!(destination instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Address must be of type InetSocketAddress.");
}
send(srcBuffer, getPathPolicy().filter(getService().getPaths((InetSocketAddress) destination)));
send(
srcBuffer,
getPathPolicy().filter(getOrCreateService().getPaths((InetSocketAddress) destination)));
}

/**
Expand Down
14 changes: 6 additions & 8 deletions src/main/java/org/scion/ScionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,6 @@
}
throw e;
}
synchronized (LOCK) {
if (DEFAULT == null) {
DEFAULT = this;
}
}
}

/**
Expand Down Expand Up @@ -171,10 +166,9 @@
DEFAULT = new ScionService(daemonHost + ":" + daemonPort, Mode.DAEMON);
return DEFAULT;
} catch (ScionRuntimeException e) {
// Ignore
throw new ScionRuntimeException(
"Could not connect to daemon, DNS or bootstrap resource.", e);
}

throw new ScionRuntimeException("Could not connect to daemon, DNS or bootstrap resource.");
}
}

Expand Down Expand Up @@ -229,6 +223,10 @@
return DatagramChannel.open(this);
}

public DatagramChannel openChannel(java.nio.channels.DatagramChannel channel) throws IOException {
return DatagramChannel.open(this, channel);

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

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/scion/ScionService.java#L227

Added line #L227 was not covered by tests
}

Daemon.ASResponse getASInfo() {
Daemon.ASRequest request = Daemon.ASRequest.newBuilder().setIsdAs(0).build();
Daemon.ASResponse response;
Expand Down
18 changes: 17 additions & 1 deletion src/test/java/org/scion/PackageVisibilityHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package org.scion;

import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.List;
import org.scion.internal.InternalConstants;
import org.scion.internal.ScionHeaderParser;
Expand Down Expand Up @@ -69,10 +71,24 @@ public static RequestPath createDummyPath(
Daemon.Interface.newBuilder()
.setAddress(Daemon.Underlay.newBuilder().setAddress(firstHopString).build())
.build();
Daemon.Path path = Daemon.Path.newBuilder().setRaw(bs).setInterface(inter).build();
Timestamp ts = Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond() + 100).build();
Daemon.Path path =
Daemon.Path.newBuilder().setRaw(bs).setInterface(inter).setExpiration(ts).build();
return RequestPath.create(path, dstIsdAs, dstHost, dstPort);
}

public static ResponsePath createDummyResponsePath(
byte[] raw,
long srcIsdAs,
byte[] srcIP,
int srcPort,
long dstIsdAs,
byte[] dstIP,
int dstPort,
InetSocketAddress firstHop) {
return ResponsePath.create(raw, srcIsdAs, srcIP, srcPort, dstIsdAs, dstIP, dstPort, firstHop);
}

public static RequestPath createRequestPath110_112(
Daemon.Path.Builder builder,
long dstIsdAs,
Expand Down
112 changes: 112 additions & 0 deletions src/test/java/org/scion/api/DatagramChannelApiServerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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.api;

import static org.junit.jupiter.api.Assertions.*;

import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.scion.*;
import org.scion.testutil.ExamplePacket;
import org.scion.testutil.MockDNS;
import org.scion.testutil.MockDaemon;
import org.scion.testutil.MockDatagramChannel;

/**
* Test that typical "server" operations do not require a ScionService.
*
* <p>This is service-less operation is required for servers that should not use (or may not have)
* access to a daemon or control service. Service-less operation is mainly implemented to ensure
* good server performance by avoiding costly network calls to daemons etc.
*/
class DatagramChannelApiServerTest {

private static final int dummyPort = 44444;

@BeforeEach
public void beforeEach() throws IOException {
MockDaemon.closeDefault();
MockDNS.clear();
ScionService.closeDefault();
}

@AfterEach
public void afterEach() throws IOException {
MockDaemon.closeDefault();
MockDNS.clear();
ScionService.closeDefault();
}

@Test
void open_withoutService() throws IOException {
// check that open() (without service argument) does not internally require a ScionService.
try (DatagramChannel channel = DatagramChannel.open()) {
assertNull(channel.getService());
}
}

@Test
void bind_withoutService() throws IOException {
// check that open() (without service argument) does not internally require a ScionService.
try (DatagramChannel channel = DatagramChannel.open()) {
channel.bind(new InetSocketAddress("127.0.0.1", 12345));
assertNull(channel.getService());
}
}

@Test
void send_withoutService() throws IOException {
// check that send(ResponsePath) does not internally require a ScionService.
try (DatagramChannel channel = DatagramChannel.open()) {
assertNull(channel.getService());
ResponsePath path =
PackageVisibilityHelper.createDummyResponsePath(
new byte[0],
1,
new byte[4],
1,
1,
new byte[4],
1,
new InetSocketAddress("127.0.0.1", 1));
Path p2 = channel.send(ByteBuffer.allocate(0), path);
assertNull(channel.getService());
assertEquals(path, p2);
}
}

@Test
void receive_withoutService() throws IOException {
// check that receive() does not internally require a ScionService.
SocketAddress addr = new InetSocketAddress("127.0.0.1", 12345);
MockDatagramChannel mock = MockDatagramChannel.open();
mock.setReceiveCallback(
buf -> {
buf.put(ExamplePacket.PACKET_BYTES_SERVER_E2E_PING);
return addr;
});
try (DatagramChannel channel = DatagramChannel.open(null, mock)) {
assertNull(channel.getService());
ByteBuffer buffer = ByteBuffer.allocate(100);
Path path = channel.receive(buffer);
assertNull(channel.getService());
assertEquals(addr, path.getFirstHopAddress());
}
}
}
Loading