Skip to content

Commit

Permalink
R & O
Browse files Browse the repository at this point in the history
  • Loading branch information
Tilmann Zäschke committed Feb 18, 2025
1 parent 41341aa commit 8770342
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 126 deletions.
50 changes: 49 additions & 1 deletion doc/PathPolicyLanguage.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ that document.

## Destination Matching

Each entry consists of a pattern (that matches destinations) and an associated path filter.
Each entry consists of a pattern (that matches destinations) and an associated path filter.
The pattern contains:

- ISD or `0` for catch all
Expand All @@ -48,6 +48,54 @@ In the example above, the destination `1-0:0:110,10.0.0.2:80` would be filtered
`filter_110a`, whereas `1-0:0:110,10.0.0.3:80` would be filtered by `filter_110b` and
`1-0:0:120,10.0.0.2:80` would be filtered by `default`.

## Ordering and Requirements

PPL also allows defining requirements and precedence ordering.
They can be defined per filter or as global default.

The following example specifies two default requirements that must be satisfied by all paths:
MTU must be >= 1340 and the path must be valid for at least 10 more seconds.
It also specifies that valid paths should be ordered primarily by hop count and secondarily by
latency.
However, filter `"filter_f10"` overrides the default MTU and sets the minimum MTU to 0.
It also changes the ordering to be only by latency.

```json
{
"destination": {
...
}
"defaults": {
"min_mtu": 1340,
"min_validity_sec": 10,
"ordering": "hops_asc,meta_latency_asc"
},
"filters": {
"filter_f10": {
"acl": [
...
],
"min_mtu": 0,
"ordering": "meta_latency_asc"
}
}
}
```

The following requirements and ordering are supported:

- `min_mtu`: minimum MTU in bytes
- `min_bandwidth`: minimum bandwidth in bps
- `min_validity_sec`: minimum validity time in seconds

The following orderings are supported:

- `hops_asc`: order by number of hops in ascending order
- `hops_desc`: order by number of hops in descending order
- `meta_bandwidth_desc`: order by metadata bandwidth in descending order (unknown bandwidth is
assumed 0)
- `meta_latency_asc`: order by metadata latency in ascending order (unknown latency is assumed 10s)

## Path Filters

Path filters are inspired by the
Expand Down
59 changes: 48 additions & 11 deletions src/main/java/org/scion/jpan/ppl/PplPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,33 @@ public class PplPolicy implements PathPolicy {

private final List<Entry> policies;
private final int minMtu;
private final long minBandwidthBPS;
private final int minValiditySec;
private final Comparator<Path> ordering;

private PplPolicy(
List<Entry> policies, int minMtuBytes, int minValiditySeconds, String ordering) {
List<Entry> policies,
int minMtuBytes,
long minBandwidthBPS,
int minValiditySeconds,
String ordering) {
this.policies = policies;
this.minMtu = minMtuBytes;
this.minBandwidthBPS = minBandwidthBPS;
this.minValiditySec = minValiditySeconds;
String[] orderings = ordering == null ? new String[0] : ordering.split(",");
this.ordering = buildComparator(orderings);
}

@Override
public List<Path> filter(List<Path> input) {
// filter
List<Path> filtered = new ArrayList<>();
long now = System.currentTimeMillis() / 1000; // unix epoch
for (Path path : input) {
PathMetadata meta = path.getMetadata();
if ((minMtu <= 0 || meta.getMtu() >= minMtu)
&& (minValiditySec <= 0 || meta.getExpiration() >= now + minValiditySec)) {
&& (minValiditySec <= 0 || meta.getExpiration() >= now + minValiditySec)
&& (minBandwidthBPS <= 0 || getMinBandwidth(path) >= minBandwidthBPS)) {
filtered.add(path);
}
}
Expand All @@ -70,6 +76,16 @@ public List<Path> filter(List<Path> input) {
return filtered;
}

private long getMinBandwidth(Path path) {
long minBandwidth = Long.MAX_VALUE;
for (long bandwidth : path.getMetadata().getBandwidthList()) {
if (bandwidth < minBandwidth) {
minBandwidth = bandwidth;
}
}
return minBandwidth;
}

private Comparator<Path> buildComparator(String[] orderings) {
if (orderings.length == 0) {
return null;
Expand Down Expand Up @@ -107,16 +123,24 @@ private Comparator<Path> getPathComparator(String ordering) {
Integer.compare(
p2.getMetadata().getInterfacesList().size(),
p1.getMetadata().getInterfacesList().size());
case "meta_bandwidth_desc":
// Unknown bw is treated as 0. Empty path is treated as MAX bandwidth
return (p1, p2) ->
Long.compare(
p2.getMetadata().getBandwidthList().stream()
.mapToLong(Long::longValue)
.min()
.orElse(Long.MAX_VALUE),
p1.getMetadata().getBandwidthList().stream()
.mapToLong(Long::longValue)
.min()
.orElse(Long.MAX_VALUE));
case "meta_latency_asc":
// -1 is mapped to 10000 to ensure that paths with missing latencies are sorted last
return Comparator.comparingInt(
p -> p.getMetadata().getLatencyList().stream().mapToInt(Integer::intValue).sum());
case "meta_latency_desc":
return (p1, p2) ->
Integer.compare(
p2.getMetadata().getLatencyList().stream().mapToInt(Integer::intValue).sum(),
p1.getMetadata().getLatencyList().stream().mapToInt(Integer::intValue).sum());
p -> p.getMetadata().getLatencyList().stream().mapToInt(l -> l < 0 ? 10000 : l).sum());
default:
throw new IllegalArgumentException("Unknown ordering: " + ordering);
throw new IllegalArgumentException("PPL: unknown ordering: " + ordering);
}
}

Expand Down Expand Up @@ -245,6 +269,7 @@ public boolean isMatch(ScionSocketAddress destination) {
public static class Builder {
private final List<Entry> list = new ArrayList<>();
private int minMtuBytes = 0;
private long minBandwidthBytesPerSeconds = 0;
private int minValiditySeconds = 0;
private String ordering = null;

Expand All @@ -253,6 +278,17 @@ public Builder add(String destination, PplPathFilter policy) {
return this;
}

/**
* Minimum metadata bandwidth requirement for paths. Default is 0.
*
* @param minBandwidthBytesPerSeconds Minimum bandwidth in bytes per second.
* @return this Builder
*/
public Builder minMetaBandwidth(int minBandwidthBytesPerSeconds) {
this.minBandwidthBytesPerSeconds = minBandwidthBytesPerSeconds;
return this;
}

/**
* Minimum MTU requirement for paths. Default is 0.
*
Expand Down Expand Up @@ -284,7 +320,8 @@ public PplPolicy build() {
if (list.isEmpty()) {
throw new PplException("Policy has no default filter");
}
return new PplPolicy(list, minMtuBytes, minValiditySeconds, ordering);
return new PplPolicy(
list, minMtuBytes, minBandwidthBytesPerSeconds, minValiditySeconds, ordering);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,36 @@
import static org.junit.jupiter.api.Assertions.*;

import java.util.*;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.scion.jpan.*;
import org.scion.jpan.ppl.PathProvider;
import org.scion.jpan.ppl.PplPathFilter;
import org.scion.jpan.ppl.PplPolicy;

/** Tests for PPL requirements and ordering. */
class PathPolicyLanguageOrderingTest {

private static final PplPathFilter ALLOW = PplPathFilter.builder().addAclEntry("+").build();

@Test
void first() {
List<Path> paths = createLongMixedList();
List<Path> filtered = PathPolicy.FIRST.filter(paths);
for (int i = 0; i < 4; i++) {
assertEquals(paths.get(i), filtered.get(i));
}
void errorOrdering_label() {
PplPolicy.Builder pb = PplPolicy.builder().add("0", ALLOW).ordering("shops_asc");
Exception e = assertThrows(IllegalArgumentException.class, pb::build);
assertTrue(e.getMessage().contains("PPL: unknown ordering: "));
}

@Test
void errorOrdering_labels() {
PplPolicy.Builder pb = PplPolicy.builder().add("0", ALLOW).ordering("hops_asc,meta_asc");
Exception e = assertThrows(IllegalArgumentException.class, pb::build);
assertTrue(e.getMessage().contains("PPL: unknown ordering: "));
}

@Disabled
@Test
void minHops() {
void hopsAsc() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
PplPolicy.builder().ordering("hops_asc").build();
List<Path> filtered = PathPolicy.MIN_HOPS.filter(pathsWithDifferentLengths);
PplPolicy policy = PplPolicy.builder().add("0", ALLOW).ordering("hops_asc").build();
List<Path> filtered = policy.filter(pathsWithDifferentLengths);
assertEquals(2, filtered.get(0).getMetadata().getInterfacesList().size());
int prevHops = 2;
for (int i = 0; i < pathsWithDifferentLengths.size(); i++) {
Expand All @@ -52,9 +58,25 @@ void minHops() {
}

@Test
void minLatency() {
void hopsDesc() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
PplPolicy policy = PplPolicy.builder().add("0", ALLOW).ordering("hops_desc").build();
List<Path> filtered = policy.filter(pathsWithDifferentLengths);
assertEquals(8, filtered.get(0).getMetadata().getInterfacesList().size());
int prevHops = 20000;
for (int i = 0; i < pathsWithDifferentLengths.size(); i++) {
int nHops = filtered.get(i).getMetadata().getInterfacesList().size();
assertTrue(nHops <= prevHops);
prevHops = nHops;
}
assertEquals(2, prevHops);
}

@Test
void metaLatencyAsc() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
List<Path> filtered = PathPolicy.MIN_LATENCY.filter(pathsWithDifferentLengths);
PplPolicy policy = PplPolicy.builder().add("0", ALLOW).ordering("meta_latency_asc").build();
List<Path> filtered = policy.filter(pathsWithDifferentLengths);
int prevLatency = 0;
for (int i = 0; i < pathsWithDifferentLengths.size(); i++) {
int localMin = 0;
Expand All @@ -76,48 +98,26 @@ void minLatency() {
}

@Test
void maxBandwidth() {
void metaBandwidthDesc() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
List<Path> filtered = PathPolicy.MAX_BANDWIDTH.filter(pathsWithDifferentLengths);
PplPolicy policy = PplPolicy.builder().add("0", ALLOW).ordering("meta_bandwidth_desc").build();
List<Path> filtered = policy.filter(pathsWithDifferentLengths);
long prevBW = Long.MAX_VALUE;
for (int i = 0; i < pathsWithDifferentLengths.size(); i++) {
long localMax = Long.MAX_VALUE;
for (Long bw : filtered.get(i).getMetadata().getBandwidthList()) {
for (long bw : filtered.get(i).getMetadata().getBandwidthList()) {
if (bw <= 0) {
localMax = 0;
break;
}
localMax = Math.min(localMax, bw);
}
if (localMax == 0) {
localMax = 0;
}

assertTrue(localMax <= prevBW);
prevBW = localMax;
}
}

@Test
void isdAllow() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
Set<Integer> allowedIsds = new HashSet<>();
allowedIsds.add(2);
PathPolicy.IsdAllow isdAllow = new PathPolicy.IsdAllow(allowedIsds);
List<Path> filtered = isdAllow.filter(pathsWithDifferentLengths);
assertEquals(4, filtered.size());
}

@Test
void isdDisallow() {
List<Path> pathsWithDifferentLengths = createLongMixedList();
Set<Integer> disallowedIsds = new HashSet<>();
disallowedIsds.add(1);
PathPolicy.IsdDisallow isdDisallow = new PathPolicy.IsdDisallow(disallowedIsds);
List<Path> filtered = isdDisallow.filter(pathsWithDifferentLengths);
assertEquals(4, filtered.size());
}

private List<Path> createLongMixedList() {
PathProvider pp = new PathProvider();
List<Path> paths1x2 = pp.getPaths("2-ff00:0:210", "1-ff00:0:110");
Expand Down
Loading

0 comments on commit 8770342

Please sign in to comment.