Skip to content

Commit ad365a1

Browse files
committed
pan: policy-based path-aware SCION application library
This is a first version of the pan "Path-Aware Networking" library. The pan library represents the path selection process for SCION connections by Policies, which filter and order the paths based on the information included in the path metadata, and Selectors, which dynamically select the final path for each packet sent. This gives a relatively simple interface for applications that directly translates to an application user interface (e.g. command line interface), and connections can automatically failover in case of SCMP errors messages. The pan library can do the work for querying and updating path information in the background. A small number of default Policies and Selectors are built-in. In the future, we might add more of these out of the box, but the idea is that applications can also define custom Policies and Selectors. See the package documentation for more information. Currently uses inet.af/netaddr to represent IP addresses. This will be replaced by the very similar net/netip once this becomes available, expectedly in go-1.18. Other than this, pan attempts to not expose external libraries in its API and provide a self-contained interface. All applications in scion-apps have been converted to pan, but these changes will be committed separately. Known issues in this first version: - the DefaultReplySelector, i.e. the path selector on the listening side, is susceptible to session hijacking by spoofing source address. - the stats DB is intentionally kept private as this requires more thought. For now, applications need to store their own stats separate from pans internal store. - internal state of DefaultReplySelector and stats DB are never cleaned up and can grow without bounds. - far too many globals Add package quicutil: - single stream quic utilities Using a single quic stream is common in our demo applications. Add a utility function for this, extracted from previous internal implementation in shttp. Incompatible to the previous version in shttp. Now either side can start sending (by using two unidirectional streams), plus it can now reliably close. The listener is also more robust; previously, if a remote session was opened without ever opening a stream, this would have blocked the listener Accept indefinitely. - generate self-signed certificate (previously in appquic) Updated example code: - extend helloworld example and port to pan. - add helloquic example, using pan Misc: - integration: use addr= instead of only ia= as "ready signal" - remove appnet/appquic from main README as these packages are slated for removal. - bugfixes backported to appnet: fix SplitHostPort (bad regex capture group index), extend addrFromString to work with addresses without the optional brackets.
1 parent 914e1ea commit ad365a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5846
-52
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ DESTDIR = $(shell set -a; eval $$( go env ); gopath=$${GOPATH%:*}; echo $${GOBIN
1212
# HINT: build with TAGS=norains to build without rains support
1313
TAGS =
1414

15-
all: lint build
15+
all: build lint
1616

1717
build: scion-bat \
1818
scion-bwtestclient scion-bwtestserver \
@@ -23,6 +23,7 @@ build: scion-bat \
2323
scion-webapp \
2424
scion-web-gateway \
2525
example-helloworld \
26+
example-helloquic \
2627
example-hellodrkey \
2728
example-shttp-client example-shttp-server example-shttp-fileserver example-shttp-proxy
2829

@@ -97,6 +98,10 @@ scion-web-gateway:
9798
example-helloworld:
9899
go build -tags=$(TAGS) -o $(BIN)/$@ ./_examples/helloworld/
99100

101+
.PHONY: example-helloquic
102+
example-helloquic:
103+
go build -tags=$(TAGS) -o $(BIN)/$@ ./_examples/helloquic/
104+
100105
.PHONY: example-shttp-client
101106
example-shttp-client:
102107
go build -tags=$(TAGS) -o $(BIN)/$@ ./_examples/shttp/client

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,16 @@ resolver, in the form `<ISD>-<AS>,[<IP>]`.
9393

9494
## _examples
9595

96-
The directory _examples contains a minimal "hello, world" application using SCION that sends one packet from a client to a server,
97-
as well as a simple "hello DRKey" application, showing how to use DRKey.
98-
The directory also contains small example programs that show how HTTP can be used over SCION/QUIC for servers, proxies, and clients.
99-
100-
More documentation is available in the [helloworld README](_examples/helloworld/README.md), in the [hellodrkey README](_examples/hellodrkey/README.md)
101-
and in the [shttp README](_examples/shttp/README.md).
96+
The directory _examples contains examples for the usage of the SCION libraries.
10297

98+
* [_examples/helloworld](_examples/helloworld/README.md):
99+
A minimal "hello, world" application using UDP over SCION.
100+
* [_examples/helloquic](_examples/helloquic/README.md):
101+
Example for the use of QUIC over SCION.
102+
* [_examples/hellodrkey](_examples/hellodrkey/README.md):
103+
Example for the the use of DRKey.
104+
* [_examples/shttp](_examples/shttp/README.md):
105+
Examples for using HTTP over SCION/QUIC, examples for servers, proxies, and clients.
103106

104107
## bat
105108

@@ -124,10 +127,10 @@ netcat contains a SCION port of the netcat application. See the [netcat README](
124127

125128
Pkg contains underlaying library code for scion-apps.
126129

127-
- appnet: simplified and functionally extended wrapper interfaces for the SCION core libraries
128-
- appquic: a simple interface to use QUIC over SCION
130+
- pan: Policy-based, path aware networking library, wrapper for the SCION core libraries
129131
- shttp: glue library to use net/http libraries for HTTP over SCION
130132
- shttp3: glue library to use quic-go/http3 for HTTP/3 over SCION
133+
- quicutil: contains utilities for working with QUIC
131134
- integration: a simple framework to support intergration testing for the demo applications in this repository
132135

133136

_examples/helloquic/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Hello QUIC
2+
3+
A simple echo server, demonstrating the use of QUIC over SCION.
4+
The client creates a QUIC stream per echo request. The server reads the
5+
entire stream content and echos it back to the client.
6+
7+
Server:
8+
```
9+
go run helloquic.go -port 1234
10+
```
11+
12+
Client:
13+
```
14+
go run helloquic.go -remote 17-ffaa:1:a,[127.0.0.1]:1234
15+
```
16+
17+
Replace `17-ffaa:1:a` with the address of the AS in which the server is running.

_examples/helloquic/helloquic.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2021 ETH Zurich
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"context"
19+
"crypto/tls"
20+
"errors"
21+
"flag"
22+
"fmt"
23+
"io/ioutil"
24+
"os"
25+
"time"
26+
27+
"github.com/lucas-clemente/quic-go"
28+
"inet.af/netaddr"
29+
30+
"github.com/netsec-ethz/scion-apps/pkg/pan"
31+
"github.com/netsec-ethz/scion-apps/pkg/quicutil"
32+
)
33+
34+
func main() {
35+
var err error
36+
// get local and remote addresses from program arguments:
37+
var listen pan.IPPortValue
38+
flag.Var(&listen, "listen", "[Server] local IP:port to listen on")
39+
remoteAddr := flag.String("remote", "", "[Client] Remote (i.e. the server's) SCION Address (e.g. 17-ffaa:1:1,[127.0.0.1]:12345)")
40+
count := flag.Uint("count", 1, "[Client] Number of messages to send")
41+
flag.Parse()
42+
43+
if (listen.Get().Port() > 0) == (len(*remoteAddr) > 0) {
44+
check(fmt.Errorf("Either specify -port for server or -remote for client"))
45+
}
46+
47+
if listen.Get().Port() > 0 {
48+
err = runServer(listen.Get())
49+
check(err)
50+
} else {
51+
err = runClient(*remoteAddr, int(*count))
52+
check(err)
53+
}
54+
}
55+
56+
func runServer(listen netaddr.IPPort) error {
57+
tlsCfg := &tls.Config{
58+
Certificates: quicutil.MustGenerateSelfSignedCert(),
59+
NextProtos: []string{"hello-quic"},
60+
}
61+
listener, err := pan.ListenQUIC(context.Background(), listen, nil, tlsCfg, nil)
62+
if err != nil {
63+
return err
64+
}
65+
defer listener.Close()
66+
fmt.Println(listener.Addr())
67+
68+
for {
69+
session, err := listener.Accept(context.Background())
70+
if err != nil {
71+
return err
72+
}
73+
fmt.Println("New session", session.RemoteAddr())
74+
go func() {
75+
err := workSession(session)
76+
if err != nil && !errors.Is(err, &quic.ApplicationError{}) {
77+
fmt.Println("Error in session", session.RemoteAddr(), err)
78+
}
79+
}()
80+
}
81+
}
82+
83+
func workSession(session quic.Session) error {
84+
for {
85+
stream, err := session.AcceptStream(context.Background())
86+
if err != nil {
87+
return err
88+
}
89+
defer stream.Close()
90+
data, err := ioutil.ReadAll(stream)
91+
if err != nil {
92+
return err
93+
}
94+
fmt.Printf("%s\n", data)
95+
_, err = stream.Write([]byte("gotcha: "))
96+
_, err = stream.Write(data)
97+
if err != nil {
98+
return err
99+
}
100+
stream.Close()
101+
}
102+
}
103+
104+
func runClient(address string, count int) error {
105+
addr, err := pan.ResolveUDPAddr(address)
106+
if err != nil {
107+
return err
108+
}
109+
tlsCfg := &tls.Config{
110+
InsecureSkipVerify: true,
111+
NextProtos: []string{"hello-quic"},
112+
}
113+
// Set Pinging Selector with active probing on two paths
114+
selector := &pan.PingingSelector{
115+
Interval: 2 * time.Second,
116+
Timeout: time.Second,
117+
}
118+
selector.SetActive(2)
119+
session, err := pan.DialQUIC(context.Background(), netaddr.IPPort{}, addr, nil, selector, "", tlsCfg, nil)
120+
if err != nil {
121+
return err
122+
}
123+
for i := 0; i < count; i++ {
124+
stream, err := session.OpenStream()
125+
if err != nil {
126+
return err
127+
}
128+
_, err = stream.Write([]byte(fmt.Sprintf("hi dude, %d", i)))
129+
if err != nil {
130+
return err
131+
}
132+
stream.Close()
133+
reply, err := ioutil.ReadAll(stream)
134+
fmt.Printf("%s\n", reply)
135+
}
136+
session.CloseWithError(quic.ApplicationErrorCode(0), "")
137+
return nil
138+
}
139+
140+
// Check just ensures the error is nil, or complains and quits
141+
func check(e error) {
142+
if e != nil {
143+
fmt.Fprintln(os.Stderr, "Fatal error:", e)
144+
os.Exit(1)
145+
}
146+
}

_examples/helloworld/README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Hello World
22

3-
A simple application using SCION that sends one packet from a client to a server.
3+
A simple application using SCION that sends one packet from a client to a server
4+
which replies back.
45

56
Server:
67
```
@@ -18,14 +19,15 @@ Replace `17-ffaa:1:a` with the address of the AS in which the server is running.
1819

1920
This SCION application is very simple, and it demonstrates what is needed to send data using SCION:
2021

21-
1. Validate command-line arguments.
2222

2323
Server:
24-
2. Open listener connection (appnet.ListenPort).
25-
3. Read packets from connection.
26-
4. Close listener connection.
24+
1. Open listener connection (`pan.ListenUDP`).
25+
1. Read packets from connection (`conn.ReadFrom`).
26+
1. Write reply packet (`conn.WriteTo`).
27+
1. Close listener connection.
2728

2829
Client:
29-
2. Open client connection (appnet.Dial).
30-
3. Write packet to connection.
31-
4. Close client connection.
30+
1. Open client connection (`pan.Dial`).
31+
1. Write packet to connection (`conn.Write`).
32+
1. Read reply packet (`conn.Read`), with timeout
33+
1. Close client connection.

_examples/helloworld/helloworld.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,47 @@
1515
package main
1616

1717
import (
18+
"context"
19+
"errors"
1820
"flag"
1921
"fmt"
2022
"os"
23+
"time"
2124

22-
"github.com/netsec-ethz/scion-apps/pkg/appnet"
25+
"inet.af/netaddr"
26+
27+
"github.com/netsec-ethz/scion-apps/pkg/pan"
2328
)
2429

2530
func main() {
2631
var err error
2732
// get local and remote addresses from program arguments:
28-
port := flag.Uint("port", 0, "[Server] local port to listen on")
33+
var listen pan.IPPortValue
34+
flag.Var(&listen, "listen", "[Server] local IP:port to listen on")
2935
remoteAddr := flag.String("remote", "", "[Client] Remote (i.e. the server's) SCION Address (e.g. 17-ffaa:1:1,[127.0.0.1]:12345)")
36+
count := flag.Uint("count", 1, "[Client] Number of messages to send")
3037
flag.Parse()
3138

32-
if (*port > 0) == (len(*remoteAddr) > 0) {
33-
check(fmt.Errorf("Either specify -port for server or -remote for client"))
39+
if (listen.Get().Port() > 0) == (len(*remoteAddr) > 0) {
40+
check(fmt.Errorf("Either specify -listen for server or -remote for client"))
3441
}
3542

36-
if *port > 0 {
37-
err = runServer(uint16(*port))
43+
if listen.Get().Port() > 0 {
44+
err = runServer(listen.Get())
3845
check(err)
3946
} else {
40-
err = runClient(*remoteAddr)
47+
err = runClient(*remoteAddr, int(*count))
4148
check(err)
4249
}
4350
}
4451

45-
func runServer(port uint16) error {
46-
conn, err := appnet.ListenPort(port)
52+
func runServer(listen netaddr.IPPort) error {
53+
conn, err := pan.ListenUDP(context.Background(), listen, nil)
4754
if err != nil {
4855
return err
4956
}
5057
defer conn.Close()
58+
fmt.Println(conn.LocalAddr())
5159

5260
buffer := make([]byte, 16*1024)
5361
for {
@@ -57,28 +65,48 @@ func runServer(port uint16) error {
5765
}
5866
data := buffer[:n]
5967
fmt.Printf("Received %s: %s\n", from, data)
68+
msg := fmt.Sprintf("take it back! %s", time.Now().Format("15:04:05.0"))
69+
n, err = conn.WriteTo([]byte(msg), from)
70+
fmt.Printf("Wrote %d bytes.\n", n)
6071
}
6172
}
6273

63-
func runClient(address string) error {
64-
conn, err := appnet.Dial(address)
74+
func runClient(address string, count int) error {
75+
addr, err := pan.ResolveUDPAddr(address)
6576
if err != nil {
6677
return err
6778
}
68-
defer conn.Close()
69-
70-
nBytes, err := conn.Write([]byte("hello world"))
79+
conn, err := pan.DialUDP(context.Background(), netaddr.IPPort{}, addr, nil, nil)
7180
if err != nil {
7281
return err
7382
}
74-
fmt.Printf("Done. Wrote %d bytes.\n", nBytes)
83+
defer conn.Close()
84+
85+
for i := 0; i < count; i++ {
86+
nBytes, err := conn.Write([]byte(fmt.Sprintf("hello world %s", time.Now().Format("15:04:05.0"))))
87+
if err != nil {
88+
return err
89+
}
90+
fmt.Printf("Wrote %d bytes.\n", nBytes)
91+
92+
buffer := make([]byte, 16*1024)
93+
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
94+
n, err := conn.Read(buffer)
95+
if errors.Is(err, os.ErrDeadlineExceeded) {
96+
continue
97+
} else if err != nil {
98+
return err
99+
}
100+
data := buffer[:n]
101+
fmt.Printf("Received reply: %s\n", data)
102+
}
75103
return nil
76104
}
77105

78106
// Check just ensures the error is nil, or complains and quits
79107
func check(e error) {
80108
if e != nil {
81-
fmt.Fprintln(os.Stderr, "Fatal error. Exiting.", "err", e)
109+
fmt.Fprintln(os.Stderr, "Fatal error:", e)
82110
os.Exit(1)
83111
}
84112
}

_examples/helloworld/helloworld_integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ func TestHelloworldSample(t *testing.T) {
3434
cmd := integration.AppBinPath(bin)
3535
// Server
3636
serverPort := "12345"
37-
serverArgs := []string{"-port", serverPort}
37+
serverArgs := []string{"-listen", ":" + serverPort}
3838

3939
// Client
4040
clientArgs := []string{"-remote", integration.DstAddrPattern + ":" + serverPort}
4141

4242
in := integration.NewAppsIntegration(cmd, cmd, clientArgs, serverArgs)
43-
in.ServerOutMatch = integration.Contains("hello world")
44-
in.ClientOutMatch = integration.Contains("Done. Wrote 11 bytes.")
43+
in.ServerOutMatch = integration.RegExp("(?m)^Received .*: hello world .*\nWrote 24 bytes")
44+
in.ClientOutMatch = integration.RegExp("(?m)^Wrote 22 bytes.\nReceived reply: take it back! .*")
4545
// Cartesian product of src and dst IAs, a random permutation
4646
// restricted to a subset to reduce the number of tests to run without significant
4747
// loss of coverage

0 commit comments

Comments
 (0)