├── .circleci └── config.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── _examples ├── .golangci.yml ├── go.mod ├── go.sum ├── helloquic │ ├── README.md │ └── helloquic.go ├── helloworld │ ├── README.md │ ├── helloworld.go │ └── helloworld_integration_test.go ├── sgrpc │ ├── README.md │ ├── client │ │ └── main.go │ ├── proto │ │ ├── echo.pb.go │ │ ├── echo.proto │ │ └── echo_grpc.pb.go │ └── server │ │ └── main.go └── shttp │ ├── README.md │ ├── client │ └── main.go │ ├── fileserver │ └── main.go │ ├── proxy │ └── main.go │ └── server │ ├── dog.jpg │ └── main.go ├── bat ├── LICENSE ├── README.md ├── bat.go ├── bench.go ├── color.go ├── filter.go ├── http.go ├── httplib │ ├── README.md │ ├── httplib.go │ └── httplib_test.go ├── images │ └── bat_output.png ├── pb.go └── utils.go ├── bwtester ├── README.md ├── bwtest │ └── bwtest.go ├── bwtestclient │ ├── bwtestclient.go │ ├── bwtestclient_integration_test.go │ └── bwtestclient_test.go └── bwtestserver │ └── bwtestserver.go ├── go.mod ├── go.sum ├── netcat ├── README.md ├── main.go ├── netcat_integration_test.go ├── quic.go └── udp.go ├── pkg ├── integration │ ├── apps.go │ ├── integration.go │ ├── script.go │ └── sintegration │ │ └── sintegration.go ├── pan │ ├── addr.go │ ├── addr_test.go │ ├── cli.go │ ├── def.go │ ├── dns_txt.go │ ├── dns_txt_test.go │ ├── flag.go │ ├── flag_test.go │ ├── hosts.go │ ├── hosts_test.go │ ├── hosts_test_file │ ├── hostsfile.go │ ├── interactive.go │ ├── internal │ │ └── ping │ │ │ └── ping.go │ ├── pan.go │ ├── pan_test.go │ ├── path.go │ ├── path_metadata.go │ ├── path_test.go │ ├── policy.go │ ├── policy_test.go │ ├── pool.go │ ├── quic_dial.go │ ├── quic_listen.go │ ├── rains.go │ ├── raw.go │ ├── refresher.go │ ├── sciond.go │ ├── selector.go │ ├── silence.go │ ├── stats.go │ ├── stats_test.go │ ├── udp_dial.go │ ├── udp_listen.go │ └── udp_listen_test.go ├── quicutil │ ├── single.go │ └── tls.go ├── shttp │ ├── README.md │ ├── server.go │ ├── transport.go │ └── transport_test.go └── shttp3 │ ├── README.md │ ├── server.go │ └── transport.go ├── sensorapp ├── sensorapp_integration_test.go ├── sensorfetcher │ └── sensorfetcher.go └── sensorserver │ ├── sensorreader.py │ ├── sensorserver.go │ └── timereader.py ├── skip ├── README.md ├── main.go └── skip.pac ├── ssh ├── README.md ├── client │ ├── clientconfig │ │ ├── clientconfig.go │ │ └── clientconfig_test.go │ ├── main.go │ └── ssh │ │ ├── knownhosts │ │ └── knownhosts.go │ │ ├── scion.go │ │ ├── selector.go │ │ ├── shell.go │ │ └── ssh.go ├── config │ ├── config.go │ └── config_test.go ├── scp │ └── scp.sh ├── server │ ├── main.go │ ├── serverconfig │ │ └── serverconfig.go │ └── ssh │ │ ├── authcallbacks.go │ │ ├── sessionchannel.go │ │ ├── ssh.go │ │ └── tunnelchannel.go └── utils │ └── path-utils.go ├── web-gateway ├── README.md └── main.go └── webapp ├── README.md ├── dependencies.md ├── development.md ├── lib ├── bwcont.go ├── config.go ├── echocont.go ├── health.go ├── sciond.go └── traceroutecont.go ├── models ├── bwtests.go ├── db.go ├── path │ ├── db.go │ └── segments.go ├── scmpecho.go └── traceroute.go ├── tests ├── asviz │ ├── config-d.json │ ├── geolocate-d.json │ ├── json_as_topo.json │ ├── json_crt.html │ ├── json_path_topo.json │ ├── json_paths.json │ ├── json_seg_topo.json │ ├── json_trc.html │ ├── labels-d.json │ ├── nodes-d.xml │ └── path_info.html └── run_servers.sh ├── util └── log.go ├── web ├── config │ └── servers_default.json ├── favicon.ico ├── static │ ├── css │ │ ├── animation.css │ │ ├── bootstrap.min.css │ │ ├── pretty-checkbox.min.css │ │ ├── style-viz.css │ │ ├── style.css │ │ └── topology.css │ ├── html │ │ └── map.html │ ├── img │ │ └── lock-locked.svg │ └── js │ │ ├── asviz.js │ │ ├── bootstrap.min.js │ │ ├── cola.v3.min.js │ │ ├── d3.v3.min.js │ │ ├── highcharts.js │ │ ├── jquery-2.2.0.min.js │ │ ├── jquery.knob.min.js │ │ ├── location.js │ │ ├── tab-g-maps.js │ │ ├── tab-paths.js │ │ ├── tab-topocola.js │ │ ├── topojson.min.js │ │ ├── topology.js │ │ └── webapp.js ├── template │ ├── about.html │ ├── astopo.html │ ├── error.html │ ├── files.html │ ├── footer.html │ ├── header.html │ ├── health.html │ ├── index.html │ └── trc.html └── tests │ └── health │ ├── beaconstest.sh │ ├── bincheck.sh │ ├── default.json │ ├── netcheck.sh │ ├── scmpcheck.sh │ ├── testAvailDiskSpace.sh │ ├── testSCIONRunning.sh │ ├── testTotalMem.sh │ ├── testVPN.sh │ └── timecheck.sh └── webapp.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Emacs temp files 17 | *~ 18 | 19 | # User-defined webapp node addresses 20 | webapp/web/config/clients_default.json 21 | webapp/web/config/clients_user.json 22 | webapp/web/config/servers_user.json 23 | 24 | # Generated webapp data and logging 25 | webapp/web/webapp.db 26 | webapp/web/data 27 | .webapp 28 | 29 | # IDE project files 30 | .jshintrc 31 | .project 32 | .settings/ 33 | 34 | *.log 35 | 36 | # vendor directory 37 | vendor/*/ 38 | 39 | # generated binaries 40 | bin/ 41 | 42 | # vscode workspace 43 | *.code-workspace 44 | 45 | # IntelliJ IDEA workspace 46 | .idea/ 47 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Configuration for golangci-lint for scion-apps. 2 | # See https://github.com/golangci/golangci-lint#config-file 3 | 4 | linters: 5 | enable: 6 | # enabled by default: 7 | #- deadcode 8 | #- errcheck 9 | #- gosimple 10 | #- govet 11 | #- ineffassign 12 | #- staticcheck 13 | #- structcheck 14 | #- typecheck 15 | #- unused 16 | #- varcheck 17 | - asciicheck 18 | - bidichk 19 | - contextcheck 20 | - dupl 21 | - durationcheck 22 | - errname 23 | - errorlint 24 | - exportloopref 25 | - gci 26 | - gofmt 27 | #- ifshort 28 | - importas 29 | - makezero 30 | - misspell 31 | - nilerr 32 | - nilnil 33 | - nolintlint 34 | - prealloc 35 | - stylecheck 36 | - thelper 37 | - wastedassign 38 | - tenv 39 | 40 | issues: 41 | # XXX exclude some linters where there are too many issues to fix now 42 | exclude-rules: 43 | - path: webapp/ 44 | linters: 45 | - stylecheck 46 | - staticcheck 47 | - errcheck 48 | # Bat is mostly copied third-party code that we don't care to fix 49 | # Modifications are only (?) in bat/bat.go 50 | exclude-dirs: 51 | - bat/*/ 52 | exclude-files: 53 | - bat/color.go 54 | - bat/bench.go 55 | - bat/pb.go 56 | - bat/http.go 57 | max-same-issues: 0 58 | 59 | linters-settings: 60 | gci: 61 | local-prefixes: github.com/netsec-ethz/scion-apps 62 | exhaustive: 63 | default-signifies-exhaustive: true 64 | 65 | run: 66 | build-tags: integration 67 | -------------------------------------------------------------------------------- /_examples/.golangci.yml: -------------------------------------------------------------------------------- 1 | # Configuration for golangci-lint for scion-apps. 2 | # See https://github.com/golangci/golangci-lint#config-file 3 | 4 | linters: 5 | enable: 6 | # enabled by default: 7 | #- deadcode 8 | #- errcheck 9 | #- gosimple 10 | #- govet 11 | #- ineffassign 12 | #- staticcheck 13 | #- structcheck 14 | #- typecheck 15 | #- unused 16 | #- varcheck 17 | - asciicheck 18 | - bidichk 19 | - contextcheck 20 | - dupl 21 | - durationcheck 22 | - errname 23 | - errorlint 24 | - exportloopref 25 | - gci 26 | - gofmt 27 | #- ifshort 28 | - importas 29 | - makezero 30 | - misspell 31 | - nilerr 32 | - nilnil 33 | - nolintlint 34 | - prealloc 35 | - stylecheck 36 | - thelper 37 | - wastedassign 38 | - tenv 39 | 40 | issues: 41 | max-same-issues: 0 42 | 43 | linters-settings: 44 | gci: 45 | local-prefixes: examples 46 | exhaustive: 47 | default-signifies-exhaustive: true 48 | 49 | run: 50 | build-tags: integration 51 | -------------------------------------------------------------------------------- /_examples/go.mod: -------------------------------------------------------------------------------- 1 | module examples 2 | 3 | go 1.22.7 4 | 5 | toolchain go1.22.10 6 | 7 | require ( 8 | github.com/golang/protobuf v1.5.4 9 | github.com/gorilla/handlers v1.5.1 10 | github.com/netsec-ethz/scion-apps v0.5.0 11 | github.com/quic-go/quic-go v0.43.1 12 | google.golang.org/grpc v1.63.2 13 | ) 14 | 15 | require ( 16 | github.com/antlr4-go/antlr/v4 v4.13.1 // indirect 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/britram/borat v0.0.0-20181011130314-f891bcfcfb9b // indirect 19 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 20 | github.com/dchest/cmac v1.0.0 // indirect 21 | github.com/dustin/go-humanize v1.0.1 // indirect 22 | github.com/felixge/httpsnoop v1.0.1 // indirect 23 | github.com/go-stack/stack v1.8.0 // indirect 24 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 25 | github.com/google/gopacket v1.1.19 // indirect 26 | github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect 27 | github.com/google/uuid v1.6.0 // indirect 28 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect 29 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 30 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect 31 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 32 | github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec // indirect 33 | github.com/mattn/go-colorable v0.1.13 // indirect 34 | github.com/mattn/go-isatty v0.0.20 // indirect 35 | github.com/mattn/go-sqlite3 v1.14.22 // indirect 36 | github.com/ncruces/go-strftime v0.1.9 // indirect 37 | github.com/netsec-ethz/rains v0.5.1-0.20240619143424-8e9ef27f2403 // indirect 38 | github.com/onsi/ginkgo/v2 v2.17.3 // indirect 39 | github.com/opentracing/opentracing-go v1.2.0 // indirect 40 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 41 | github.com/prometheus/client_golang v1.19.1 // indirect 42 | github.com/prometheus/client_model v0.6.1 // indirect 43 | github.com/prometheus/common v0.53.0 // indirect 44 | github.com/prometheus/procfs v0.14.0 // indirect 45 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 46 | github.com/scionproto/scion v0.12.1-0.20241223103250-0b42cbc42486 // indirect 47 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 48 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 49 | go.uber.org/atomic v1.11.0 // indirect 50 | go.uber.org/mock v0.4.0 // indirect 51 | go.uber.org/multierr v1.11.0 // indirect 52 | go.uber.org/zap v1.27.0 // indirect 53 | golang.org/x/crypto v0.31.0 // indirect 54 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 55 | golang.org/x/mod v0.17.0 // indirect 56 | golang.org/x/net v0.25.0 // indirect 57 | golang.org/x/sys v0.28.0 // indirect 58 | golang.org/x/text v0.21.0 // indirect 59 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 60 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect 61 | google.golang.org/protobuf v1.34.1 // indirect 62 | gopkg.in/yaml.v2 v2.4.0 // indirect 63 | modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect 64 | modernc.org/libc v1.50.5 // indirect 65 | modernc.org/mathutil v1.6.0 // indirect 66 | modernc.org/memory v1.8.0 // indirect 67 | modernc.org/sqlite v1.29.9 // indirect 68 | modernc.org/strutil v1.2.0 // indirect 69 | modernc.org/token v1.1.0 // indirect 70 | ) 71 | 72 | replace github.com/netsec-ethz/scion-apps => ../ 73 | -------------------------------------------------------------------------------- /_examples/helloquic/README.md: -------------------------------------------------------------------------------- 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 -listen 127.0.0.1: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. 18 | -------------------------------------------------------------------------------- /_examples/helloworld/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | A simple application using SCION that sends one packet from a client to a server 4 | which replies back. 5 | 6 | Server: 7 | ``` 8 | go run helloworld.go -listen 127.0.0.1:1234 9 | ``` 10 | 11 | Client: 12 | ``` 13 | go run helloworld.go -remote 17-ffaa:1:a,[127.0.0.1]:1234 14 | ``` 15 | 16 | Replace `17-ffaa:1:a` with the address of the AS in which the server is running. 17 | 18 | ## Walkthrough: 19 | 20 | This SCION application is very simple, and it demonstrates what is needed to send data using SCION: 21 | 22 | 23 | Server: 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. 28 | 29 | Client: 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. 34 | -------------------------------------------------------------------------------- /_examples/helloworld/helloworld.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | "errors" 20 | "flag" 21 | "fmt" 22 | "net/netip" 23 | "os" 24 | "time" 25 | 26 | "github.com/netsec-ethz/scion-apps/pkg/pan" 27 | ) 28 | 29 | func main() { 30 | var err error 31 | // get local and remote addresses from program arguments: 32 | var listen pan.IPPortValue 33 | flag.Var(&listen, "listen", "[Server] local IP:port to listen on") 34 | remoteAddr := flag.String("remote", "", "[Client] Remote (i.e. the server's) SCION Address (e.g. 17-ffaa:1:1,[127.0.0.1]:12345)") 35 | count := flag.Uint("count", 1, "[Client] Number of messages to send") 36 | flag.Parse() 37 | 38 | if (listen.Get().Port() > 0) == (len(*remoteAddr) > 0) { 39 | check(fmt.Errorf("either specify -listen for server or -remote for client")) 40 | } 41 | 42 | if listen.Get().Port() > 0 { 43 | err = runServer(listen.Get()) 44 | check(err) 45 | } else { 46 | err = runClient(*remoteAddr, int(*count)) 47 | check(err) 48 | } 49 | } 50 | 51 | func runServer(listen netip.AddrPort) error { 52 | conn, err := pan.ListenUDP(context.Background(), listen) 53 | if err != nil { 54 | return err 55 | } 56 | defer conn.Close() 57 | fmt.Println(conn.LocalAddr()) 58 | 59 | buffer := make([]byte, 16*1024) 60 | for { 61 | n, from, err := conn.ReadFrom(buffer) 62 | if err != nil { 63 | return err 64 | } 65 | data := buffer[:n] 66 | fmt.Printf("Received %s: %s\n", from, data) 67 | msg := fmt.Sprintf("take it back! %s", time.Now().Format("15:04:05.0")) 68 | n, err = conn.WriteTo([]byte(msg), from) 69 | if err != nil { 70 | return err 71 | } 72 | fmt.Printf("Wrote %d bytes.\n", n) 73 | } 74 | } 75 | 76 | func runClient(address string, count int) error { 77 | addr, err := pan.ResolveUDPAddr(context.TODO(), address) 78 | if err != nil { 79 | return err 80 | } 81 | conn, err := pan.DialUDP(context.Background(), netip.AddrPort{}, addr) 82 | if err != nil { 83 | return err 84 | } 85 | defer conn.Close() 86 | 87 | for i := 0; i < count; i++ { 88 | nBytes, err := conn.Write([]byte(fmt.Sprintf("hello world %s", time.Now().Format("15:04:05.0")))) 89 | if err != nil { 90 | return err 91 | } 92 | fmt.Printf("Wrote %d bytes.\n", nBytes) 93 | 94 | buffer := make([]byte, 16*1024) 95 | if err = conn.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil { 96 | return err 97 | } 98 | n, err := conn.Read(buffer) 99 | if errors.Is(err, os.ErrDeadlineExceeded) { 100 | continue 101 | } else if err != nil { 102 | return err 103 | } 104 | data := buffer[:n] 105 | fmt.Printf("Received reply: %s\n", data) 106 | } 107 | return nil 108 | } 109 | 110 | // Check just ensures the error is nil, or complains and quits 111 | func check(e error) { 112 | if e != nil { 113 | fmt.Fprintln(os.Stderr, "Fatal error:", e) 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /_examples/helloworld/helloworld_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | //go:build integration 16 | // +build integration 17 | 18 | package main 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/netsec-ethz/scion-apps/pkg/integration" 24 | ) 25 | 26 | const ( 27 | bin = "example-helloworld" 28 | ) 29 | 30 | func TestMain(m *testing.M) { 31 | integration.TestMain(m) 32 | } 33 | 34 | func TestHelloworldSample(t *testing.T) { 35 | cmd := integration.AppBinPath(bin) 36 | // Server 37 | serverPortOffset := 12345 38 | serverArgs := []string{"-listen", ":" + integration.ServerPortReplace} 39 | 40 | // Client 41 | clientArgs := []string{ 42 | "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, 43 | } 44 | 45 | in := integration.NewAppsIntegration(cmd, cmd, clientArgs, serverArgs) 46 | in.ServerOutMatch = integration.RegExp("(?m)^Received .*: hello world .*\nWrote 24 bytes") 47 | in.ClientOutMatch = integration.RegExp("(?m)^Wrote 22 bytes.\nReceived reply: take it back! .*") 48 | // Cartesian product of src and dst IAs, a random permutation 49 | // restricted to a subset to reduce the number of tests to run without significant 50 | // loss of coverage 51 | iaPairs := integration.DefaultIAPairs() 52 | // Add different ports to servers. 53 | integration.AssignUniquePorts(iaPairs, serverPortOffset, 2) 54 | // Run the tests to completion or until a test fails, 55 | // increase the ClientTimeout if clients need more time to start 56 | if err := in.Run(t, iaPairs); err != nil { 57 | t.Error(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /_examples/sgrpc/README.md: -------------------------------------------------------------------------------- 1 | # Example for gRPC over SCION/QUIC 2 | 3 | This directory contains a small example program that shows how gRPC can be used over SCION/QUIC with the PAN library. 4 | The example consists of an echo server and a client that sends a message to the service. 5 | 6 | ## Running 7 | 8 | The example requires a running SCION endhost stack, i.e. a running SCION dispatcher and SCION daemon. Please refer to '[Running](../../README.md#Running)' in this repository's main README and the [SCIONLab tutorials](https://docs.scionlab.org) to get started. 9 | See '[Environment](../../README.md#Environment)' on how to set the dispatcher and sciond environment variables when e.g. running multiple local ASes. 10 | 11 | To test the server and client, run the SCION tiny test topology. 12 | 13 | Open a shell and run the server in the AS `1-ff00:0:111`: 14 | ```bash 15 | # Server in 1-ff00:0:111 16 | SCION_DAEMON_ADDRESS="127.0.0.20:30255" \ 17 | go run server/main.go --server-address 127.0.0.1:5000 18 | ``` 19 | 20 | Open a shell and run the client in the AS `1-ff00:0:112` and send a message to the server: 21 | ```bash 22 | # Client in 1-ff00:0:112 23 | SCION_DAEMON_ADDRESS="127.0.0.28:30255" \ 24 | go run client/main.go --server-address "1-ff00:0:111,127.0.0.1:5000" --message "gRPC over SCION/QUIC" 25 | ``` 26 | 27 | ## Protobuf 28 | Tutorial: https://grpc.io/docs/languages/go/basics/ 29 | 30 | Requirements: 31 | ```bash 32 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 33 | ``` 34 | 35 | The compiler plugin protoc-gen-go will be installed in $GOBIN, defaulting to $GOPATH/bin. It must be in your $PATH for the protocol compiler protoc to find it. 36 | ```bash 37 | export GO_PATH=~/go 38 | export PATH=$PATH:/$GO_PATH/bin 39 | ``` 40 | 41 | The generation of the gRPC client and server interface is performed as follows: 42 | ```bash 43 | protoc --go_out=. --go_opt=paths=source_relative \ 44 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 45 | proto/*.proto 46 | ``` 47 | -------------------------------------------------------------------------------- /_examples/sgrpc/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/netip" 11 | "time" 12 | 13 | "github.com/netsec-ethz/scion-apps/pkg/pan" 14 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 15 | "github.com/quic-go/quic-go" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials/insecure" 18 | 19 | pb "examples/sgrpc/proto" 20 | ) 21 | 22 | var ( 23 | Message = flag.String("message", "", "Message to send to the gRPC echo server") 24 | ServerAddr = flag.String("server-addr", "1-ff00:0:111,127.0.0.1:5000", "Address of the echo server") 25 | ) 26 | 27 | func NewPanQuicDialer(tlsCfg *tls.Config) func(context.Context, string) (net.Conn, error) { 28 | dialer := func(ctx context.Context, addr string) (net.Conn, error) { 29 | panAddr, err := pan.ResolveUDPAddr(ctx, addr) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | clientQuicConfig := &quic.Config{KeepAlivePeriod: 15 * time.Second} 35 | session, err := pan.DialQUIC(ctx, netip.AddrPort{}, panAddr, "", tlsCfg, clientQuicConfig) 36 | if err != nil { 37 | return nil, fmt.Errorf("did not dial: %w", err) 38 | } 39 | return quicutil.NewSingleStream(session) 40 | } 41 | 42 | return dialer 43 | } 44 | 45 | func main() { 46 | flag.Parse() 47 | 48 | tlsCfg := &tls.Config{ 49 | InsecureSkipVerify: true, 50 | NextProtos: []string{"echo_service"}, 51 | } 52 | 53 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 54 | defer cancel() 55 | 56 | grpcDial, err := grpc.DialContext(ctx, *ServerAddr, 57 | grpc.WithContextDialer(NewPanQuicDialer(tlsCfg)), 58 | grpc.WithTransportCredentials(insecure.NewCredentials()), 59 | ) 60 | if err != nil { 61 | log.Fatalf("failed to dial gRPC: %v", err) 62 | } 63 | 64 | c := pb.NewEchoServiceClient(grpcDial) 65 | 66 | req := &pb.EchoRequest{ 67 | Msg: *Message, 68 | } 69 | resp, err := c.Echo(ctx, req) 70 | if err != nil { 71 | log.Fatalf("gRPC did not connect: %v", err) 72 | } 73 | 74 | fmt.Println(resp.Msg) 75 | } 76 | -------------------------------------------------------------------------------- /_examples/sgrpc/proto/echo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto.echo; 4 | 5 | option go_package = "examples/sgrpc/proto"; 6 | 7 | service EchoService{ 8 | rpc Echo(EchoRequest) returns (EchoResponse) {} 9 | } 10 | 11 | message EchoRequest{ 12 | string msg = 1; 13 | } 14 | 15 | message EchoResponse{ 16 | string msg = 1; 17 | } 18 | -------------------------------------------------------------------------------- /_examples/sgrpc/proto/echo_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.12.4 5 | // source: proto/echo.proto 6 | 7 | package proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // EchoServiceClient is the client API for EchoService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type EchoServiceClient interface { 25 | Echo(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*EchoResponse, error) 26 | } 27 | 28 | type echoServiceClient struct { 29 | cc grpc.ClientConnInterface 30 | } 31 | 32 | func NewEchoServiceClient(cc grpc.ClientConnInterface) EchoServiceClient { 33 | return &echoServiceClient{cc} 34 | } 35 | 36 | func (c *echoServiceClient) Echo(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*EchoResponse, error) { 37 | out := new(EchoResponse) 38 | err := c.cc.Invoke(ctx, "/proto.echo.EchoService/Echo", in, out, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return out, nil 43 | } 44 | 45 | // EchoServiceServer is the server API for EchoService service. 46 | // All implementations must embed UnimplementedEchoServiceServer 47 | // for forward compatibility 48 | type EchoServiceServer interface { 49 | Echo(context.Context, *EchoRequest) (*EchoResponse, error) 50 | mustEmbedUnimplementedEchoServiceServer() 51 | } 52 | 53 | // UnimplementedEchoServiceServer must be embedded to have forward compatible implementations. 54 | type UnimplementedEchoServiceServer struct { 55 | } 56 | 57 | func (UnimplementedEchoServiceServer) Echo(context.Context, *EchoRequest) (*EchoResponse, error) { 58 | return nil, status.Errorf(codes.Unimplemented, "method Echo not implemented") 59 | } 60 | func (UnimplementedEchoServiceServer) mustEmbedUnimplementedEchoServiceServer() {} 61 | 62 | // UnsafeEchoServiceServer may be embedded to opt out of forward compatibility for this service. 63 | // Use of this interface is not recommended, as added methods to EchoServiceServer will 64 | // result in compilation errors. 65 | type UnsafeEchoServiceServer interface { 66 | mustEmbedUnimplementedEchoServiceServer() 67 | } 68 | 69 | func RegisterEchoServiceServer(s grpc.ServiceRegistrar, srv EchoServiceServer) { 70 | s.RegisterService(&EchoService_ServiceDesc, srv) 71 | } 72 | 73 | func _EchoService_Echo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 74 | in := new(EchoRequest) 75 | if err := dec(in); err != nil { 76 | return nil, err 77 | } 78 | if interceptor == nil { 79 | return srv.(EchoServiceServer).Echo(ctx, in) 80 | } 81 | info := &grpc.UnaryServerInfo{ 82 | Server: srv, 83 | FullMethod: "/proto.echo.EchoService/Echo", 84 | } 85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 86 | return srv.(EchoServiceServer).Echo(ctx, req.(*EchoRequest)) 87 | } 88 | return interceptor(ctx, in, info, handler) 89 | } 90 | 91 | // EchoService_ServiceDesc is the grpc.ServiceDesc for EchoService service. 92 | // It's only intended for direct use with grpc.RegisterService, 93 | // and not to be introspected or modified (even as a copy) 94 | var EchoService_ServiceDesc = grpc.ServiceDesc{ 95 | ServiceName: "proto.echo.EchoService", 96 | HandlerType: (*EchoServiceServer)(nil), 97 | Methods: []grpc.MethodDesc{ 98 | { 99 | MethodName: "Echo", 100 | Handler: _EchoService_Echo_Handler, 101 | }, 102 | }, 103 | Streams: []grpc.StreamDesc{}, 104 | Metadata: "proto/echo.proto", 105 | } 106 | -------------------------------------------------------------------------------- /_examples/sgrpc/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "log" 8 | "net/netip" 9 | 10 | "github.com/netsec-ethz/scion-apps/pkg/pan" 11 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 12 | "google.golang.org/grpc" 13 | 14 | pb "examples/sgrpc/proto" 15 | ) 16 | 17 | type echoServer struct { 18 | pb.UnimplementedEchoServiceServer 19 | } 20 | 21 | var _ pb.EchoServiceServer = &echoServer{} 22 | 23 | func (*echoServer) Echo(ctx context.Context, 24 | req *pb.EchoRequest) (*pb.EchoResponse, error) { 25 | resp := &pb.EchoResponse{ 26 | Msg: req.Msg, 27 | } 28 | return resp, nil 29 | } 30 | 31 | var ( 32 | ServerAddr = flag.String("server-addr", "127.0.0.1:5000", "Address the server should listen on") 33 | ) 34 | 35 | func main() { 36 | flag.Parse() 37 | 38 | addr, err := netip.ParseAddrPort(*ServerAddr) 39 | if err != nil { 40 | log.Fatalf("failed to parse server address") 41 | } 42 | 43 | echoServer := &echoServer{} 44 | grpcServer := grpc.NewServer() 45 | pb.RegisterEchoServiceServer(grpcServer, echoServer) 46 | 47 | tlsCfg := &tls.Config{ 48 | Certificates: quicutil.MustGenerateSelfSignedCert(), 49 | NextProtos: []string{"echo_service"}, 50 | } 51 | 52 | quicListener, err := pan.ListenQUIC(context.Background(), addr, tlsCfg, nil) 53 | if err != nil { 54 | log.Fatalf("failed to listen SCION QUIC on %s: %v", *ServerAddr, err) 55 | } 56 | lis := quicutil.SingleStreamListener{QUICListener: quicListener} 57 | log.Println("listen on", quicListener.Addr()) 58 | 59 | if err := grpcServer.Serve(lis); err != nil { 60 | log.Fatalf("failed to serve: %v", err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /_examples/shttp/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | "flag" 19 | "fmt" 20 | "io" 21 | "log" 22 | "net/http" 23 | "os" 24 | "strings" 25 | "time" 26 | 27 | "github.com/netsec-ethz/scion-apps/pkg/shttp" 28 | ) 29 | 30 | func main() { 31 | serverAddrStr := flag.String("s", "", "Server address ( or , optionally with appended <:port>)") 32 | flag.Parse() 33 | 34 | if len(*serverAddrStr) == 0 { 35 | flag.Usage() 36 | os.Exit(2) 37 | } 38 | 39 | // Create a standard client with our custom Transport/Dialer 40 | c := &http.Client{ 41 | Transport: shttp.DefaultTransport, 42 | } 43 | 44 | // Make a get request 45 | start := time.Now() 46 | query := fmt.Sprintf("http://%s/hello", *serverAddrStr) 47 | resp, err := c.Get(shttp.MangleSCIONAddrURL(query)) 48 | if err != nil { 49 | log.Fatal("GET request failed: ", err) 50 | } 51 | defer resp.Body.Close() 52 | end := time.Now() 53 | 54 | log.Printf("\nGET request succeeded in %v seconds", end.Sub(start).Seconds()) 55 | printResponse(resp) 56 | 57 | // (just for demonstration on how to use Close. Clients are safe for concurrent use and should be re-used) 58 | c.CloseIdleConnections() 59 | 60 | start = time.Now() 61 | query = fmt.Sprintf("http://%s/form", *serverAddrStr) 62 | resp, err = c.Post( 63 | shttp.MangleSCIONAddrURL(query), 64 | "application/x-www-form-urlencoded", 65 | strings.NewReader("surname=threepwood&firstname=guybrush"), 66 | ) 67 | if err != nil { 68 | log.Fatal("POST request failed: ", err) 69 | } 70 | defer resp.Body.Close() 71 | end = time.Now() 72 | 73 | log.Printf("POST request succeeded in %v seconds", end.Sub(start).Seconds()) 74 | printResponse(resp) 75 | } 76 | 77 | func printResponse(resp *http.Response) { 78 | fmt.Println("\n***Printing Response***") 79 | fmt.Println("Status: ", resp.Status) 80 | fmt.Println("Protocol:", resp.Proto) 81 | fmt.Println("Content-Length: ", resp.ContentLength) 82 | fmt.Println("Content-Type: ", resp.Header.Get("Content-Type")) 83 | body, err := io.ReadAll(resp.Body) 84 | if err != nil { 85 | log.Print(err) 86 | } 87 | if len(body) != 0 { 88 | fmt.Println("Body: ", string(body)) 89 | } 90 | fmt.Print("\n\n") 91 | } 92 | -------------------------------------------------------------------------------- /_examples/shttp/fileserver/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | // example-shttp-fileserver is a simple HTTP fileserver that serves all files 16 | // and subdirectories under the current working directory. 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "net/http" 23 | "os" 24 | 25 | "github.com/gorilla/handlers" 26 | "github.com/netsec-ethz/scion-apps/pkg/shttp" 27 | ) 28 | 29 | func main() { 30 | certFile := flag.String("cert", "", "Path to TLS server certificate for optional https") 31 | keyFile := flag.String("key", "", "Path to TLS server key for optional https") 32 | strictSCION := flag.String("strict", "", "Sets the `Strict-SCION` header value; "+ 33 | "directives similar as in the HSTS header are to be defined by this flag") 34 | flag.Parse() 35 | 36 | handler := handlers.LoggingHandler( 37 | os.Stdout, 38 | func(h http.Handler) http.HandlerFunc { 39 | return func(w http.ResponseWriter, r *http.Request) { 40 | if *strictSCION != "" { 41 | // Set Strict-SCION response header, overwrites any existing header for that key 42 | w.Header().Set("Strict-SCION", *strictSCION) 43 | } 44 | // Serve 45 | h.ServeHTTP(w, r) 46 | } 47 | }(http.FileServer(http.Dir(""))), 48 | ) 49 | if *certFile != "" && *keyFile != "" { 50 | go func() { log.Fatal(shttp.ListenAndServeTLS(":443", *certFile, *keyFile, handler)) }() 51 | } 52 | log.Fatal(shttp.ListenAndServe(":80", handler)) 53 | } 54 | -------------------------------------------------------------------------------- /_examples/shttp/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "os" 11 | 12 | "github.com/gorilla/handlers" 13 | "github.com/netsec-ethz/scion-apps/pkg/shttp" 14 | ) 15 | 16 | func main() { 17 | port := flag.Uint("port", 80, "port the proxy server listens on.") 18 | listenSCION := flag.Bool("listen-scion", false, "proxy server listens on SCION.") 19 | remote := flag.String("remote", "", "remote URL to which requests will be forwarded."+ 20 | "Requests are sent over SCION iff this contains a SCION address.") 21 | 22 | flag.Parse() 23 | 24 | if *remote == "" { 25 | flag.Usage() 26 | os.Exit(2) 27 | } 28 | remoteMangled := shttp.MangleSCIONAddrURL(*remote) 29 | remoteURL, err := url.Parse(remoteMangled) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | proxy := httputil.NewSingleHostReverseProxy(remoteURL) 34 | if remoteMangled != *remote { 35 | proxy.Transport = shttp.DefaultTransport 36 | log.Printf("Proxy to SCION remote %s\n", remoteURL) 37 | } else { 38 | log.Printf("Proxy to IP/TCP remote %s\n", remoteURL) 39 | } 40 | handler := handlers.LoggingHandler(os.Stdout, proxy) 41 | 42 | local := fmt.Sprintf(":%d", *port) 43 | if *listenSCION { 44 | log.Printf("Listen on SCION %s\n", local) 45 | // ListenAndServe does not support listening on a complete SCION Address, 46 | // Consequently, we only use the port (as seen in the server example) 47 | log.Fatal(shttp.ListenAndServe(local, handler)) 48 | } else { 49 | log.Printf("Listen on IP/TCP %s\n", local) 50 | log.Fatal(http.ListenAndServe(local, handler)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /_examples/shttp/server/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netsec-ethz/scion-apps/55667b489898af09ae9d8290410da0be176549f9/_examples/shttp/server/dog.jpg -------------------------------------------------------------------------------- /_examples/shttp/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "log" 22 | "net/http" 23 | "os" 24 | "time" 25 | 26 | "github.com/gorilla/handlers" 27 | "github.com/netsec-ethz/scion-apps/pkg/shttp" 28 | ) 29 | 30 | func main() { 31 | certFile := flag.String("cert", "", "Path to TLS server certificate for optional https") 32 | keyFile := flag.String("key", "", "Path to TLS server key for optional https") 33 | flag.Parse() 34 | 35 | m := http.NewServeMux() 36 | 37 | // handler that responds with a friendly greeting 38 | m.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { 39 | // Status 200 OK will be set implicitly 40 | w.Header().Set("Content-Type", "text/plain") 41 | _, _ = w.Write([]byte(`Oh, hello!`)) 42 | }) 43 | 44 | // handler that responds with an image file 45 | m.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) { 46 | // serve the sample JPG file 47 | // Status 200 OK will be set implicitly 48 | // Content-Length will be inferred by server 49 | // Content-Type will be detected by server 50 | http.ServeFile(w, r, "dog.jpg") 51 | }) 52 | 53 | // GET handler that responds with some json data 54 | m.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) { 55 | if r.Method == http.MethodGet { 56 | data := struct { 57 | Time string 58 | Agent string 59 | Proto string 60 | Message string 61 | }{ 62 | Time: time.Now().Format("2006.01.02 15:04:05"), 63 | Agent: r.UserAgent(), 64 | Proto: r.Proto, 65 | Message: "success", 66 | } 67 | resp, _ := json.Marshal(data) 68 | w.Header().Set("Content-Type", "application/json") 69 | fmt.Fprint(w, string(resp)) 70 | } else { 71 | http.Error(w, "wrong method: "+r.Method, http.StatusForbidden) 72 | } 73 | }) 74 | 75 | // POST handler that responds by parsing form values and returns them as string 76 | m.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) { 77 | if r.Method == http.MethodPost { 78 | if err := r.ParseForm(); err != nil { 79 | http.Error(w, "invalid form data", http.StatusBadRequest) 80 | return 81 | } 82 | w.Header().Set("Content-Type", "text/plain") 83 | fmt.Fprint(w, "received following data:\n") 84 | for s := range r.PostForm { 85 | fmt.Fprint(w, s, "=", r.PostFormValue(s), "\n") 86 | } 87 | } else { 88 | http.Error(w, "wrong method: "+r.Method, http.StatusForbidden) 89 | } 90 | }) 91 | 92 | handler := handlers.LoggingHandler(os.Stdout, m) 93 | if *certFile != "" && *keyFile != "" { 94 | go func() { log.Fatal(shttp.ListenAndServeTLS(":443", *certFile, *keyFile, handler)) }() 95 | } 96 | log.Fatal(shttp.ListenAndServe(":80", handler)) 97 | } 98 | -------------------------------------------------------------------------------- /bat/README.md: -------------------------------------------------------------------------------- 1 | # bat 2 | 3 | ![](images/bat_output.png "sample output of bat application") 4 | 5 | Go implemented CLI cURL-like tool for humans. Bat can be used for testing, debugging, and generally interacting with HTTP servers. 6 | 7 | This repository is a fork of [astaxie/bat](https://github.com/astaxie/bat) making it available for SCION/QUIC. 8 | Refer to the original repository for general usage. 9 | 10 | ### Usage 11 | 12 | ``` 13 | bat 14 | ``` 15 | 16 | The scheme defaults to HTTPS -- HTTP is not supported. The method defaults to GET in case there is no data to be sent and to POST otherwise. 17 | 18 | URLs can use SCION addresses or hostnames. Hostnames are resolved by scanning the `/etc/hosts` file or by a RAINS lookup (if configured) -- see the toplevel README. 19 | 20 | ### Examples 21 | 22 | | Request | Explanation | 23 | | --------------------------------------------------- | ------------------------------------------------------------------ | 24 | | bat server:8080/api/download | HTTPS GET request to server:8080/download | 25 | | bat 17-ffaa:1:10,[10.0.8.100]:8080/api/download | HTTPS GET request to 17-ffaa:1:10,[10.0.8.100]:8080/download | 26 | | bat -b server:8080/api/download | Run a benchmark against server:8080/download | 27 | | bat server:8080/api/upload foo=bar | HTTPS POST request with JSON encoded data
to server:8080/upload | 28 | | bat -f server:8080/api/upload foo=bar | HTTPS POST request with URL encoded data
to server:8080/upload | 29 | | bat -body "Hello World" POST server:8080/api/upload | HTTPS POST request with raw data
to server:8080/upload | 30 | -------------------------------------------------------------------------------- /bat/color.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | Gray = uint8(iota + 90) 12 | Red 13 | Green 14 | Yellow 15 | Blue 16 | Magenta 17 | Cyan 18 | White 19 | 20 | EndColor = "\033[0m" 21 | ) 22 | 23 | func Color(str string, color uint8) string { 24 | return fmt.Sprintf("%s%s%s", ColorStart(color), str, EndColor) 25 | } 26 | 27 | func ColorStart(color uint8) string { 28 | return fmt.Sprintf("\033[%dm", color) 29 | } 30 | 31 | func ColorfulRequest(str string) string { 32 | lines := strings.Split(str, "\n") 33 | if printOption&printReqHeader == printReqHeader { 34 | strs := strings.Split(lines[0], " ") 35 | strs[0] = Color(strs[0], Magenta) 36 | strs[1] = Color(strs[1], Cyan) 37 | strs[2] = Color(strs[2], Magenta) 38 | lines[0] = strings.Join(strs, " ") 39 | } 40 | for i, line := range lines[1:] { 41 | substr := strings.Split(line, ":") 42 | if len(substr) < 2 { 43 | continue 44 | } 45 | substr[0] = Color(substr[0], Gray) 46 | substr[1] = Color(strings.Join(substr[1:], ":"), Cyan) 47 | lines[i+1] = strings.Join(substr[:2], ":") 48 | } 49 | return strings.Join(lines, "\n") 50 | } 51 | 52 | func ColorfulResponse(str, contenttype string) string { 53 | match, err := regexp.MatchString(contentJsonRegex, contenttype) 54 | if err != nil { 55 | log.Fatalln("failed to compile regex", err) 56 | } 57 | if match { 58 | str = ColorfulJson(str) 59 | } else { 60 | str = ColorfulHTML(str) 61 | } 62 | return str 63 | } 64 | 65 | func ColorfulJson(str string) string { 66 | var rsli []rune 67 | var key, val, startcolor, endcolor, startsemicolon bool 68 | var prev rune 69 | for _, char := range []rune(str) { 70 | switch char { 71 | case ' ': 72 | rsli = append(rsli, char) 73 | case '{': 74 | startcolor = true 75 | key = true 76 | val = false 77 | rsli = append(rsli, char) 78 | case '}': 79 | startcolor = false 80 | endcolor = false 81 | key = false 82 | val = false 83 | rsli = append(rsli, char) 84 | case '"': 85 | if startsemicolon && prev == '\\' { 86 | rsli = append(rsli, char) 87 | } else { 88 | if startcolor { 89 | rsli = append(rsli, char) 90 | if key { 91 | rsli = append(rsli, []rune(ColorStart(Magenta))...) 92 | } else if val { 93 | rsli = append(rsli, []rune(ColorStart(Cyan))...) 94 | } 95 | startsemicolon = true 96 | key = false 97 | val = false 98 | startcolor = false 99 | } else { 100 | rsli = append(rsli, []rune(EndColor)...) 101 | rsli = append(rsli, char) 102 | endcolor = true 103 | startsemicolon = false 104 | } 105 | } 106 | case ',': 107 | if !startsemicolon { 108 | startcolor = true 109 | key = true 110 | val = false 111 | if !endcolor { 112 | rsli = append(rsli, []rune(EndColor)...) 113 | endcolor = true 114 | } 115 | } 116 | rsli = append(rsli, char) 117 | case ':': 118 | if !startsemicolon { 119 | key = false 120 | val = true 121 | startcolor = true 122 | if !endcolor { 123 | rsli = append(rsli, []rune(EndColor)...) 124 | endcolor = true 125 | } 126 | } 127 | rsli = append(rsli, char) 128 | case '\n', '\r', '[', ']': 129 | rsli = append(rsli, char) 130 | default: 131 | if !startsemicolon { 132 | if key && startcolor { 133 | rsli = append(rsli, []rune(ColorStart(Magenta))...) 134 | key = false 135 | startcolor = false 136 | endcolor = false 137 | } 138 | if val && startcolor { 139 | rsli = append(rsli, []rune(ColorStart(Cyan))...) 140 | val = false 141 | startcolor = false 142 | endcolor = false 143 | } 144 | } 145 | rsli = append(rsli, char) 146 | } 147 | prev = char 148 | } 149 | return string(rsli) 150 | } 151 | 152 | func ColorfulHTML(str string) string { 153 | return Color(str, Green) 154 | } 155 | -------------------------------------------------------------------------------- /bat/filter.go: -------------------------------------------------------------------------------- 1 | // Modifications copyright 2018 ETH Zurich 2 | // This file has been modified to make it compatible with SCION 3 | 4 | package main 5 | 6 | import ( 7 | "log" 8 | "strings" 9 | ) 10 | 11 | var methodList = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} 12 | 13 | func filter(args []string) []string { 14 | var i int 15 | if inSlice(strings.ToUpper(args[i]), methodList) { 16 | *method = strings.ToUpper(args[i]) 17 | i++ 18 | } else if len(args) > 0 && *method == "GET" { 19 | for _, v := range args[1:] { 20 | // defaults to either GET (with no request data) or POST (with request data). 21 | // Params 22 | strs := strings.Split(v, "=") 23 | if len(strs) == 2 { 24 | *method = "POST" 25 | break 26 | } 27 | // files 28 | strs = strings.Split(v, "@") 29 | if len(strs) == 2 { 30 | *method = "POST" 31 | break 32 | } 33 | } 34 | } else if *method == "GET" && body != "" { 35 | *method = "POST" 36 | } 37 | if len(args) <= i { 38 | log.Fatal("Miss the URL") 39 | } 40 | *URL = args[i] 41 | i++ 42 | 43 | return args[i:] 44 | } 45 | -------------------------------------------------------------------------------- /bat/http.go: -------------------------------------------------------------------------------- 1 | // Modifications copyright 2018 ETH Zurich 2 | // This file has been modified to make it compatible with SCION 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "io" 10 | "log" 11 | "net/http" 12 | "os" 13 | "regexp" 14 | "strings" 15 | "time" 16 | 17 | "github.com/netsec-ethz/scion-apps/bat/httplib" 18 | ) 19 | 20 | var defaultSetting = httplib.BeegoHttpSettings{ 21 | ShowDebug: true, 22 | UserAgent: "bat/" + version, 23 | ConnectTimeout: 60 * time.Second, 24 | ReadWriteTimeout: 60 * time.Second, 25 | Gzip: true, 26 | DumpBody: true, 27 | } 28 | 29 | func getHTTP(method string, url string, args []string) (r *httplib.BeegoHttpRequest) { 30 | r = httplib.NewBeegoRequest(url, method) 31 | r.Setting(defaultSetting) 32 | r.Header("Accept-Encoding", "gzip, deflate") 33 | if *isjson { 34 | r.Header("Accept", "application/json") 35 | } else if form || method == "GET" { 36 | r.Header("Accept", "*/*") 37 | } else { 38 | r.Header("Accept", "application/json") 39 | } 40 | for i := range args { 41 | // Headers 42 | strs := strings.Split(args[i], ":") 43 | if len(strs) >= 2 { 44 | if strs[0] == "Host" { 45 | r.SetHost(strings.Join(strs[1:], ":")) 46 | } 47 | r.Header(strs[0], strings.Join(strs[1:], ":")) 48 | continue 49 | } 50 | // files 51 | strs = strings.SplitN(args[i], "@", 2) 52 | if !*isjson && len(strs) == 2 { 53 | if !form { 54 | log.Fatal("file upload only support in forms style: -f=true") 55 | } 56 | r.PostFile(strs[0], strs[1]) 57 | continue 58 | } 59 | // Json raws 60 | strs = strings.SplitN(args[i], ":=", 2) 61 | if len(strs) == 2 { 62 | if strings.HasPrefix(strs[1], "@") { 63 | f, err := os.Open(strings.TrimLeft(strs[1], "@")) 64 | if err != nil { 65 | log.Fatal("Read File", strings.TrimLeft(strs[1], "@"), err) 66 | } 67 | content, err := io.ReadAll(f) 68 | if err != nil { 69 | log.Fatal("ReadAll from File", strings.TrimLeft(strs[1], "@"), err) 70 | } 71 | var j interface{} 72 | err = json.Unmarshal(content, &j) 73 | if err != nil { 74 | log.Fatal("Read from File", strings.TrimLeft(strs[1], "@"), "Unmarshal", err) 75 | } 76 | jsonmap[strs[0]] = j 77 | continue 78 | } 79 | jsonmap[strs[0]] = toRealType(strs[1]) 80 | continue 81 | } 82 | // Params 83 | strs = strings.SplitN(args[i], "=", 2) 84 | if len(strs) == 2 { 85 | if strings.HasPrefix(strs[1], "@") { 86 | f, err := os.Open(strings.TrimLeft(strs[1], "@")) 87 | if err != nil { 88 | log.Fatal("Read File", strings.TrimLeft(strs[1], "@"), err) 89 | } 90 | content, err := io.ReadAll(f) 91 | if err != nil { 92 | log.Fatal("ReadAll from File", strings.TrimLeft(strs[1], "@"), err) 93 | } 94 | strs[1] = string(content) 95 | } 96 | if form || method == "GET" { 97 | r.Param(strs[0], strs[1]) 98 | } else { 99 | jsonmap[strs[0]] = strs[1] 100 | } 101 | continue 102 | } 103 | } 104 | if !form && len(jsonmap) > 0 { 105 | r.JsonBody(jsonmap) 106 | } 107 | return 108 | } 109 | 110 | func formatResponseBody(res *http.Response, httpreq *httplib.BeegoHttpRequest, pretty bool) string { 111 | body, err := httpreq.Bytes() 112 | if err != nil { 113 | log.Fatalln("can't get the url", err) 114 | } 115 | match, err := regexp.MatchString(contentJsonRegex, res.Header.Get("Content-Type")) 116 | if err != nil { 117 | log.Fatalln("failed to compile regex", err) 118 | } 119 | if pretty && match { 120 | var output bytes.Buffer 121 | err := json.Indent(&output, body, "", " ") 122 | if err != nil { 123 | log.Fatal("Response Json Indent: ", err) 124 | } 125 | 126 | return output.String() 127 | } 128 | 129 | return string(body) 130 | } 131 | -------------------------------------------------------------------------------- /bat/httplib/README.md: -------------------------------------------------------------------------------- 1 | # httplib 2 | httplib is an libs help you to curl remote url. 3 | 4 | # How to use? 5 | 6 | ## GET 7 | you can use Get to crawl data. 8 | 9 | import "github.com/astaxie/beego/httplib" 10 | 11 | str, err := httplib.Get("http://beego.me/").String() 12 | if err != nil { 13 | // error 14 | } 15 | fmt.Println(str) 16 | 17 | ## POST 18 | POST data to remote url 19 | 20 | req := httplib.Post("http://beego.me/") 21 | req.Param("username","astaxie") 22 | req.Param("password","123456") 23 | str, err := req.String() 24 | if err != nil { 25 | // error 26 | } 27 | fmt.Println(str) 28 | 29 | ## Set timeout 30 | 31 | The default timeout is `60` seconds, function prototype: 32 | 33 | SetTimeout(connectTimeout, readWriteTimeout time.Duration) 34 | 35 | Exmaple: 36 | 37 | // GET 38 | httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) 39 | 40 | // POST 41 | httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) 42 | 43 | 44 | ## Debug 45 | 46 | If you want to debug the request info, set the debug on 47 | 48 | httplib.Get("http://beego.me/").Debug(true) 49 | 50 | ## Set HTTP Basic Auth 51 | 52 | str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String() 53 | if err != nil { 54 | // error 55 | } 56 | fmt.Println(str) 57 | 58 | ## Set HTTPS 59 | 60 | If request url is https, You can set the client support TSL: 61 | 62 | httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) 63 | 64 | More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config 65 | 66 | ## Set HTTP Version 67 | 68 | some servers need to specify the protocol version of HTTP 69 | 70 | httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1") 71 | 72 | ## Set Cookie 73 | 74 | some http request need setcookie. So set it like this: 75 | 76 | cookie := &http.Cookie{} 77 | cookie.Name = "username" 78 | cookie.Value = "astaxie" 79 | httplib.Get("http://beego.me/").SetCookie(cookie) 80 | 81 | ## Upload file 82 | 83 | httplib support mutil file upload, use `req.PostFile()` 84 | 85 | req := httplib.Post("http://beego.me/") 86 | req.Param("username","astaxie") 87 | req.PostFile("uploadfile1", "httplib.pdf") 88 | str, err := req.String() 89 | if err != nil { 90 | // error 91 | } 92 | fmt.Println(str) 93 | 94 | 95 | See godoc for further documentation and examples. 96 | 97 | * [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib) 98 | -------------------------------------------------------------------------------- /bat/images/bat_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netsec-ethz/scion-apps/55667b489898af09ae9d8290410da0be176549f9/bat/images/bat_output.png -------------------------------------------------------------------------------- /bat/utils.go: -------------------------------------------------------------------------------- 1 | // Modifications copyright 2018 ETH Zurich 2 | // This file has been modified to make it compatible with SCION 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func inSlice(str string, l []string) bool { 13 | for i := range l { 14 | if l[i] == str { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | func toRealType(str string) interface{} { 22 | if i, err := isint(str); err == nil { 23 | return i 24 | } 25 | if b, err := isbool(str); err == nil { 26 | return b 27 | } 28 | if f, err := isfloat(str); err == nil { 29 | return f 30 | } 31 | if strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]") { 32 | bstr := []byte(str) 33 | strs := strings.Split(string(bstr[1:len(bstr)-1]), ",") 34 | var r []interface{} 35 | for _, s := range strs { 36 | if i, err := isint(s); err == nil { 37 | r = append(r, i) 38 | continue 39 | } 40 | if i, err := isbool(s); err == nil { 41 | r = append(r, i) 42 | continue 43 | } 44 | if i, err := isfloat(s); err == nil { 45 | r = append(r, i) 46 | continue 47 | } 48 | r = append(r, strings.Trim(s, "\"' ")) 49 | } 50 | return r 51 | } 52 | return str 53 | } 54 | 55 | func isint(v string) (i int, err error) { 56 | return strconv.Atoi(v) 57 | } 58 | 59 | func isbool(v string) (bool, error) { 60 | return strconv.ParseBool(v) 61 | } 62 | 63 | func isfloat(v string) (float64, error) { 64 | return strconv.ParseFloat(v, 64) 65 | } 66 | 67 | // Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B 68 | func FormatBytes(i int64) (result string) { 69 | switch { 70 | case i > (1024 * 1024 * 1024 * 1024): 71 | result = fmt.Sprintf("%#.02f TB", float64(i)/1024/1024/1024/1024) 72 | case i > (1024 * 1024 * 1024): 73 | result = fmt.Sprintf("%#.02f GB", float64(i)/1024/1024/1024) 74 | case i > (1024 * 1024): 75 | result = fmt.Sprintf("%#.02f MB", float64(i)/1024/1024) 76 | case i > 1024: 77 | result = fmt.Sprintf("%#.02f KB", float64(i)/1024) 78 | default: 79 | result = fmt.Sprintf("%d B", i) 80 | } 81 | result = strings.Trim(result, " ") 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /bwtester/README.md: -------------------------------------------------------------------------------- 1 | # bwtester 2 | 3 | The bandwidth testing application `bwtester` enables a variety of bandwidth tests on the SCION network. This document describes the design of the code and protocol. Instructions on the installation and usage are described in the main [README.md](https://github.com/netsec-ethz/scion-apps/blob/master/README.md). 4 | 5 | ## Protocol design 6 | 7 | The goal is to set up bandwidth test servers throughout the SCION network, which enable stress testing of the data plane infrastructure. 8 | 9 | To avoid server bottlenecks biasing the results, a server only allows a single client to perform a bandwidth test at a given point in time. Clients are served on a first-come-first-served basis. We limit the duration of each test to 10 seconds. 10 | 11 | A bandwidth test is parametrized by the following parameters, which is specified separately for the client->server and server->client direction: 12 | 13 | ```go 14 | type BwtestParameters struct { 15 | BwtestDuration time.Duration 16 | PacketSize int 17 | NumPackets int 18 | PrgKey []byte 19 | Port uint16 20 | } 21 | ``` 22 | 23 | The duration can be up to 10 seconds, the packet size needs to be at least 4 bytes. The duration, packet size, and number of packets determine the bandwidth, as NumPackets of size PacketSize are sent during BwtestDuration. 24 | 25 | The packet contents are filled with a Pseudo-Random Generator (PRG) based on AES, the 128-bit long key is encoded in the 16-byte long slice PrgKey. The port number determines the sending port, the receiving port is specified in the other parameter list. 26 | 27 | ## Wireline data format 28 | 29 | The wireline protocol is as follows: 30 | * 'N' new bwtest request 31 | > Request: 'N', encoded bwtest parameters client->server, encoded bwtest parameters server->client 32 | > 33 | > Success response: 'N', 0 34 | > 35 | > Failure response: 'N', number of seconds to wait until next request is sent 36 | * 'R' result request 37 | > Request: 'R', encoded client sending PRG key 38 | > 39 | > Success response: 'R', 0, encoded result data 40 | > 41 | > Not ready response: 'R', number of seconds to wait until result should be ready by 42 | > 43 | > Not found response: 'R', 127 44 | 45 | ## bwtestclient 46 | 47 | The client application reads the command line parameters and establishes two SCION UDP connections to the bwtestserver: a Control Connection (CC) and a Data Connection (DC). The port numbers for the DC are simply picked as one larger than the respective ports of the CC (the CC port numbers are passed on the command line). 48 | 49 | To achieve reliability for the initial request, it may be retried up to 5 times. If the server responds with a number of seconds to wait, that amount of time is waited off before another request is sent (as the server only serves a single client at a time). Reliability for fetching the results is achieved in the same way. 50 | 51 | ## bwtestserver 52 | 53 | The server runs a main loop that handles the CC. Not to bias the bwtest results, the server handles a single client at a time. The total time for the test is estimated, and other clients are told for how long to wait if they arrive during a running test. 54 | 55 | For each client request, the server establishes a new SCION UDP Data Connection (DC). For the traffic sent on this DC, the server uses the (reversed) path used by the client on the control channel. 56 | 57 | The server starts sending right after it established the DC. Since the client already set up the receiving function, the server->client bwtest starts right away. The client only starts sending after it receives a successful server response. 58 | 59 | The results are stored in a map, indexed by the client SCION address (ISD, AS, IP) plus the port number. To ensure that the correct results are returned, we also use the AES key of the client->server direction as identifier of the connection (to prevent an erroneous client who fetches the results too early to obtain the results of a previous run). If the results are requested too early, the server indicates how many additional seconds to wait until the results will be ready. 60 | -------------------------------------------------------------------------------- /bwtester/bwtestclient/bwtestclient_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | //go:build integration 16 | // +build integration 17 | 18 | package main 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/netsec-ethz/scion-apps/pkg/integration" 24 | ) 25 | 26 | const ( 27 | clientBin = "scion-bwtestclient" 28 | serverBin = "scion-bwtestserver" 29 | ) 30 | 31 | func TestMain(m *testing.M) { 32 | integration.TestMain(m) 33 | } 34 | 35 | func TestIntegrationBwtestclient(t *testing.T) { 36 | clientCmd := integration.AppBinPath(clientBin) 37 | serverCmd := integration.AppBinPath(serverBin) 38 | 39 | // Server 40 | serverPortOffset := 40002 41 | serverArgs := []string{"--listen=:" + integration.ServerPortReplace} 42 | // Client 43 | clientArgs := []string{ 44 | "-s", integration.DstAddrPattern + ":" + integration.ServerPortReplace, 45 | "-cs", "1,?,?,1Mbps", 46 | } 47 | 48 | in := integration.NewAppsIntegration(clientCmd, serverCmd, clientArgs, serverArgs) 49 | in.ServerOutMatch = integration.Contains("Received request") 50 | in.ClientOutMatch = integration.RegExp("(?m)^Achieved bandwidth: \\d+ bps / \\d+.\\d+ [Mk]bps$") 51 | 52 | iaPairs := integration.DefaultIAPairs() 53 | // Add different ports to servers. 54 | integration.AssignUniquePorts(iaPairs, serverPortOffset, 2) 55 | if err := in.Run(t, iaPairs); err != nil { 56 | t.Error(err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/netsec-ethz/scion-apps 2 | 3 | go 1.22.7 4 | 5 | toolchain go1.22.10 6 | 7 | require ( 8 | github.com/creack/pty v1.1.17 9 | github.com/gorilla/handlers v1.5.1 10 | github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec 11 | github.com/kormat/fmt15 v0.0.0-20181112140556-ee69fecb2656 12 | github.com/mattn/go-sqlite3 v1.14.22 13 | github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9 14 | github.com/netsec-ethz/rains v0.5.1-0.20240619143424-8e9ef27f2403 15 | github.com/pelletier/go-toml v1.9.5 16 | github.com/quic-go/quic-go v0.43.1 17 | github.com/scionproto/scion v0.12.1-0.20241223103250-0b42cbc42486 18 | github.com/smartystreets/goconvey v1.8.1 19 | github.com/stretchr/testify v1.9.0 20 | golang.org/x/crypto v0.31.0 21 | golang.org/x/term v0.27.0 22 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 23 | ) 24 | 25 | require ( 26 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 27 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 28 | github.com/antlr4-go/antlr/v4 v4.13.1 // indirect 29 | github.com/beorn7/perks v1.0.1 // indirect 30 | github.com/bgentry/speakeasy v0.1.0 // indirect 31 | github.com/britram/borat v0.0.0-20181011130314-f891bcfcfb9b // indirect 32 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 34 | github.com/dchest/cmac v1.0.0 // indirect 35 | github.com/dustin/go-humanize v1.0.1 // indirect 36 | github.com/felixge/httpsnoop v1.0.1 // indirect 37 | github.com/go-stack/stack v1.8.0 // indirect 38 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 39 | github.com/golang/protobuf v1.5.4 // indirect 40 | github.com/google/gopacket v1.1.19 // indirect 41 | github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect 42 | github.com/google/uuid v1.6.0 // indirect 43 | github.com/gopherjs/gopherjs v1.17.2 // indirect 44 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect 45 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 46 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect 47 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 48 | github.com/iancoleman/strcase v0.3.0 // indirect 49 | github.com/jtolds/gls v4.20.0+incompatible // indirect 50 | github.com/mattn/go-colorable v0.1.13 // indirect 51 | github.com/mattn/go-isatty v0.0.20 // indirect 52 | github.com/ncruces/go-strftime v0.1.9 // indirect 53 | github.com/onsi/ginkgo/v2 v2.17.3 // indirect 54 | github.com/opentracing/opentracing-go v1.2.0 // indirect 55 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 56 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 57 | github.com/prometheus/client_golang v1.19.1 // indirect 58 | github.com/prometheus/client_model v0.6.1 // indirect 59 | github.com/prometheus/common v0.53.0 // indirect 60 | github.com/prometheus/procfs v0.14.0 // indirect 61 | github.com/quic-go/qpack v0.4.0 // indirect 62 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 63 | github.com/smarty/assertions v1.16.0 // indirect 64 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 65 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 66 | go.uber.org/atomic v1.11.0 // indirect 67 | go.uber.org/mock v0.4.0 // indirect 68 | go.uber.org/multierr v1.11.0 // indirect 69 | go.uber.org/zap v1.27.0 // indirect 70 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 71 | golang.org/x/mod v0.17.0 // indirect 72 | golang.org/x/net v0.25.0 // indirect 73 | golang.org/x/sys v0.28.0 // indirect 74 | golang.org/x/text v0.21.0 // indirect 75 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 76 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect 77 | google.golang.org/grpc v1.63.2 // indirect 78 | google.golang.org/protobuf v1.34.1 // indirect 79 | gopkg.in/yaml.v2 v2.4.0 // indirect 80 | gopkg.in/yaml.v3 v3.0.1 // indirect 81 | modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect 82 | modernc.org/libc v1.50.5 // indirect 83 | modernc.org/mathutil v1.6.0 // indirect 84 | modernc.org/memory v1.8.0 // indirect 85 | modernc.org/sqlite v1.29.9 // indirect 86 | modernc.org/strutil v1.2.0 // indirect 87 | modernc.org/token v1.1.0 // indirect 88 | ) 89 | -------------------------------------------------------------------------------- /netcat/README.md: -------------------------------------------------------------------------------- 1 | # scion-netcat 2 | A SCION port of the netcat utility. 3 | 4 | 5 | ## Usage 6 | ``` 7 | ./netcat : 8 | ./netcat -l 9 | ``` 10 | 11 | See `./netcat -h` for more. 12 | -------------------------------------------------------------------------------- /netcat/netcat_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | //go:build integration 16 | // +build integration 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "testing" 23 | "time" 24 | 25 | "github.com/netsec-ethz/scion-apps/pkg/integration" 26 | ) 27 | 28 | const ( 29 | netcatBin = "scion-netcat" 30 | ) 31 | 32 | func TestMain(m *testing.M) { 33 | integration.TestMain(m) 34 | } 35 | 36 | // TestIntegrationScionNetcatCmd runs the netcat listeners in -c mode, returning a 37 | // fixed string for each newly connected client. 38 | // This mode is easiest to test here as it does not require any stdin/out redirections 39 | // and the clients can terminate successfully without interrupting them. 40 | // XXX: This is testing the "happy" path only, meaning pretty much anything 41 | // else does not currently work. 42 | func TestIntegrationScionNetcatCmd(t *testing.T) { 43 | netcatCmd := integration.AppBinPath(netcatBin) 44 | 45 | cases := []struct { 46 | name string 47 | message string 48 | flags []string 49 | }{ 50 | { 51 | name: "QUIC", 52 | message: "Hello QUIC World!", 53 | flags: nil, 54 | }, 55 | { 56 | name: "UDP", 57 | message: "Hello UDP World!", 58 | flags: []string{"-b", "-u", "-q", "50ms"}, 59 | // NOTE: we need -b as the client does not otherwise send any data to make the "query" 60 | }, 61 | } 62 | for _, tc := range cases { 63 | serverPortOffset := 1234 64 | t.Run(tc.name, func(t *testing.T) { 65 | serverArgs := concat( 66 | tc.flags, 67 | []string{"-N", "-K", "-c", 68 | "echo " + tc.message, 69 | "-l", integration.ServerPortReplace}, 70 | ) 71 | // BUG: should also work with -k, but doesn't (!?) 72 | 73 | clientScriptArgs := concat( 74 | tc.flags, 75 | []string{integration.DstAddrPattern + ":" + integration.ServerPortReplace}, 76 | ) 77 | in := integration.NewAppsIntegration(netcatCmd, netcatCmd, clientScriptArgs, serverArgs) 78 | in.ClientDelay = 250 * time.Millisecond 79 | in.ClientOutMatch = integration.RegExp(fmt.Sprintf("(?m)^%s$", tc.message)) 80 | 81 | iaPairs := integration.DefaultIAPairs() 82 | // Add different ports to servers. 83 | integration.AssignUniquePorts(iaPairs, serverPortOffset, 1) 84 | if err := in.Run(t, iaPairs); err != nil { 85 | t.Error(err) 86 | } 87 | }) 88 | } 89 | } 90 | 91 | func concat(slices ...[]string) []string { 92 | var r []string 93 | for _, s := range slices { 94 | r = append(r, s...) 95 | } 96 | return r 97 | } 98 | -------------------------------------------------------------------------------- /netcat/quic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 | "io" 21 | "net/netip" 22 | "time" 23 | 24 | "github.com/quic-go/quic-go" 25 | 26 | "github.com/netsec-ethz/scion-apps/pkg/pan" 27 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 28 | ) 29 | 30 | var ( 31 | nextProtos = []string{quicutil.SingleStreamProto} 32 | ) 33 | 34 | // DoListenQUIC listens on a QUIC socket 35 | func DoListenQUIC(port uint16) (chan io.ReadWriteCloser, error) { 36 | quicListener, err := pan.ListenQUIC( 37 | context.Background(), 38 | netip.AddrPortFrom(netip.Addr{}, port), 39 | &tls.Config{ 40 | Certificates: quicutil.MustGenerateSelfSignedCert(), 41 | NextProtos: nextProtos, 42 | }, 43 | &quic.Config{KeepAlivePeriod: 15 * time.Second}, 44 | ) 45 | if err != nil { 46 | return nil, err 47 | } 48 | listener := quicutil.SingleStreamListener{QUICListener: quicListener} 49 | 50 | conns := make(chan io.ReadWriteCloser) 51 | go func() { 52 | 53 | for { 54 | conn, err := listener.Accept() 55 | if err != nil { 56 | logError("Can't accept", "err", err) 57 | continue 58 | } 59 | conns <- conn 60 | } 61 | }() 62 | 63 | return conns, nil 64 | } 65 | 66 | // DoDialQUIC dials with a QUIC socket 67 | func DoDialQUIC(remote string, policy pan.Policy) (io.ReadWriteCloser, error) { 68 | remoteAddr, err := pan.ResolveUDPAddr(context.TODO(), remote) 69 | if err != nil { 70 | return nil, err 71 | } 72 | sess, err := pan.DialQUIC( 73 | context.Background(), 74 | netip.AddrPort{}, 75 | remoteAddr, 76 | pan.MangleSCIONAddr(remote), 77 | &tls.Config{ 78 | InsecureSkipVerify: true, 79 | NextProtos: nextProtos, 80 | }, 81 | &quic.Config{KeepAlivePeriod: 15 * time.Second}, 82 | pan.WithPolicy(policy), 83 | ) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return quicutil.NewSingleStream(sess) 89 | } 90 | -------------------------------------------------------------------------------- /netcat/udp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 | "io" 20 | "net/netip" 21 | "sync" 22 | 23 | "github.com/netsec-ethz/scion-apps/pkg/pan" 24 | ) 25 | 26 | type udpListenConn struct { 27 | requests chan<- []byte 28 | responses <-chan int 29 | mutex sync.Mutex // protects Read's requests/responses channel from concurrent Close 30 | write func(b []byte) (int, error) 31 | close func() error 32 | } 33 | 34 | func (conn *udpListenConn) Read(b []byte) (int, error) { 35 | conn.mutex.Lock() 36 | defer conn.mutex.Unlock() 37 | 38 | if conn.requests == nil { 39 | return 0, io.EOF 40 | } 41 | conn.requests <- b 42 | return <-conn.responses, nil 43 | } 44 | 45 | func (conn *udpListenConn) Write(b []byte) (int, error) { 46 | return conn.write(b) 47 | } 48 | 49 | func (conn *udpListenConn) Close() error { 50 | conn.mutex.Lock() 51 | defer conn.mutex.Unlock() 52 | return conn.close() 53 | } 54 | 55 | // DoDialUDP dials with a UDP socket 56 | func DoDialUDP(remote string, policy pan.Policy) (io.ReadWriteCloser, error) { 57 | remoteAddr, err := pan.ResolveUDPAddr(context.TODO(), remote) 58 | if err != nil { 59 | return nil, err 60 | } 61 | conn, err := pan.DialUDP(context.Background(), netip.AddrPort{}, remoteAddr, pan.WithPolicy(policy)) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return conn, nil 67 | } 68 | 69 | // DoListenUDP listens on a UDP socket 70 | func DoListenUDP(port uint16) (chan io.ReadWriteCloser, error) { 71 | conn, err := pan.ListenUDP( 72 | context.Background(), 73 | netip.AddrPortFrom(netip.Addr{}, port), 74 | ) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | readRequests := make(map[string](chan []byte)) 80 | readResponses := make(map[string](chan int)) 81 | 82 | conns := make(chan io.ReadWriteCloser) 83 | 84 | go func() { 85 | buf := make([]byte, 65536) 86 | for { 87 | n, addr, err := conn.ReadFrom(buf) 88 | if err != nil { 89 | logError("reading from UDP socket: %v", err) 90 | close(conns) 91 | return 92 | } 93 | addrStr := addr.String() 94 | 95 | nbufChan, contained := readRequests[addrStr] 96 | nrespChan := readResponses[addrStr] 97 | if !contained { 98 | // create new UDP connection 99 | logDebug("New UDP connection", "addr", addrStr) 100 | nbufChan = make(chan []byte) 101 | nrespChan = make(chan int, 1) 102 | 103 | readRequests[addrStr] = nbufChan 104 | readResponses[addrStr] = nrespChan 105 | 106 | conns <- &udpListenConn{ 107 | requests: nbufChan, 108 | responses: nrespChan, 109 | write: func(b []byte) (n int, err error) { 110 | return conn.WriteTo(b, addr) 111 | }, 112 | close: func() (err error) { 113 | close(nbufChan) 114 | delete(readRequests, addrStr) 115 | return nil 116 | }, 117 | } 118 | } 119 | 120 | // copy to the correct buffer 121 | from := 0 122 | for from < n { 123 | nbuf, open := <-nbufChan 124 | if !open { 125 | logDebug("UDP connection closed with unread data remaining in buffer, discarding it") 126 | break 127 | } 128 | written := copy(nbuf, buf[from:n]) 129 | from += written 130 | nrespChan <- written 131 | } 132 | } 133 | }() 134 | 135 | return conns, nil 136 | } 137 | -------------------------------------------------------------------------------- /pkg/integration/script.go: -------------------------------------------------------------------------------- 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 integration 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path" 21 | ) 22 | 23 | // InputPipeScript generates a shell script that runs command with input generated 24 | // by inputCommand. The script will created in tmpDir. Returns the path to the 25 | // generated script. The script will generate more stuff in tmpDir when executed, 26 | // the caller is responsible for cleaning this up. Use `(*testing.T).TempDir()`. 27 | // 28 | // The script is roughly similar to: 29 | // 30 | // #!/bin/sh 31 | // inputCommand | command "$@" 32 | // 33 | // The two commands are inserted verbatim (no escaping performed). Be careful 34 | // to wrap longer shell commands into subshells where appropriate. 35 | // 36 | // NOTE: instead of the simple pipe above, we use a named fifo, start the input 37 | // command in the background so that we can *exec* command. 38 | // This helps to ensure that the subprocesses are somewhat reliably cleaned up. 39 | // 40 | // BACKGROUND: when exec.CommandContext kills the process, it sends a SIGKILL 41 | // to only the process itself (not the process group), leaving the subprocesses 42 | // dangling. Due to some additional quirk in the processing of stdout/err in 43 | // exec.Command (go routines processing stdout/err are never stopped, 44 | // https://github.com/golang/go/issues/23019), Wait-ing on the command then 45 | // never returns. 46 | // By using exec in the shell script, we make sure that `command`, instead of 47 | // the parent shell, is the process that will actually be tracked/killed by the 48 | // golang Command. When killing `command`, this should usually also stop the 49 | // `inputCommand` simply by closing the pipe. 50 | // 51 | // NOTE: if this stops working, some alternatives are: 52 | // - avoid to shell out for the input in the first place and directly write to 53 | // stdin of the process with a goroutine. 54 | // - circumvent the stdout/err processing goroutine by using a os.Pipe which 55 | // can be explicitly closed (as suggested in https://github.com/golang/go/issues/23019) 56 | // - avoid the CommandContext, which uses Kill, and "cancel" the whole process 57 | // group explicitly by SIGTERM. 58 | // - this will NOT work: trap to clean up subprocesses in the shell script, 59 | // because CommandContext sends SIGKILL. This was a "fun" exercise. 60 | func InputPipeScript(tmpDir, name, inputCommand, command string) string { 61 | scriptPath := path.Join(tmpDir, fmt.Sprintf("%s_wrapper.sh", name)) 62 | f, err := os.OpenFile(scriptPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0744) 63 | if err != nil { 64 | panic(fmt.Sprintf("failed to create temp file: %v", err)) 65 | } 66 | defer f.Close() 67 | 68 | script := fmt.Sprintf(`#!/bin/sh 69 | fifopath=$(mktemp -d -p "%s")/fifo # make sub dir to ensure fifo does not exist 70 | mkfifo "$fifopath" 71 | %s > "$fifopath" & 72 | exec %s "$@" < "$fifopath" 73 | `, tmpDir, inputCommand, command) 74 | 75 | _, err = f.WriteString(script) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return scriptPath 80 | } 81 | -------------------------------------------------------------------------------- /pkg/pan/addr_test.go: -------------------------------------------------------------------------------- 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 pan_test 16 | 17 | import ( 18 | "fmt" 19 | "net/netip" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/netsec-ethz/scion-apps/pkg/pan" 25 | ) 26 | 27 | func TestUDPAddrIsValid(t *testing.T) { 28 | ia := pan.MustParseIA("1-ff00:0:0") 29 | iaWildcard := pan.MustParseIA("1-0") 30 | ip := netip.MustParseAddr("127.0.0.1") 31 | cases := []struct { 32 | addr pan.UDPAddr 33 | isValid bool 34 | }{ 35 | {pan.UDPAddr{}, false}, 36 | {pan.UDPAddr{IA: ia}, false}, 37 | {pan.UDPAddr{IP: ip}, false}, 38 | {pan.UDPAddr{IA: ia, IP: ip}, true}, 39 | {pan.UDPAddr{IA: iaWildcard, IP: ip}, false}, 40 | } 41 | for _, c := range cases { 42 | assert.Equal(t, c.isValid, c.addr.IsValid(), fmt.Sprintf("%s IsValid?", c.addr)) 43 | // Port does not affect IsValid 44 | withPort := c.addr.WithPort(8888) 45 | assert.Equal(t, c.isValid, withPort.IsValid(), fmt.Sprintf("%s IsValid?", withPort)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/pan/cli.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | ) 21 | 22 | var ( 23 | AvailablePreferencePolicies = []string{"latency", "bandwidth", "hops", "mtu"} 24 | preferencePolicies = map[string]Policy{ 25 | "latency": LowestLatency{}, 26 | "bandwidth": HighestBandwidth{}, 27 | "hops": LeastHops{}, 28 | "mtu": HighestMTU{}, 29 | } 30 | ) 31 | 32 | // PolicyFromCommandline is a utilty function to create a path policy 33 | // from command line options. 34 | // 35 | // The intent of this function is to help providing a somewhat 36 | // consistent CLI interface for applications using this library, 37 | // without enforcing the use of a specific command line flag 38 | // library. 39 | // 40 | // The options should be presented to the user as: 41 | // - a flag --interactive 42 | // - an option --preference , sorting order for paths. 43 | // Comma-separated list of available sorting options. 44 | // - an option --sequence , describing a hop-predicate sequence filter 45 | func PolicyFromCommandline(sequence string, preference string, interactive bool) (Policy, error) { 46 | chain := PolicyChain{} 47 | if sequence != "" { 48 | seq, err := NewSequence(sequence) 49 | if err != nil { 50 | return nil, err 51 | } 52 | chain = append(chain, seq) 53 | } 54 | if preference != "" { 55 | preferences := strings.Split(preference, ",") 56 | // apply in reverse order (least important first) 57 | for i := len(preferences) - 1; i >= 0; i-- { 58 | if p, ok := preferencePolicies[preferences[i]]; ok { 59 | chain = append(chain, p) 60 | } else { 61 | return nil, fmt.Errorf("unknown preference sorting policy '%s'", preferences[i]) 62 | } 63 | } 64 | } 65 | if interactive { 66 | chain = append(chain, &InteractiveSelection{ 67 | Prompter: CommandlinePrompter{}, 68 | }) 69 | } 70 | if len(chain) == 1 { 71 | return chain[0], nil 72 | } else { 73 | return chain, nil 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pkg/pan/def.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "time" 21 | ) 22 | 23 | var ErrNoPath = errors.New("no path") 24 | 25 | func errNoPathTo(ia IA) error { 26 | return fmt.Errorf("%w to %s", ErrNoPath, ia) 27 | } 28 | 29 | const ( 30 | // pathRefreshMinInterval is the minimum time between two path refreshs 31 | pathRefreshMinInterval = 10 * time.Second 32 | // pathRefreshInterval is the refresh interval in case no paths are expiring, i.e. the interval 33 | // in which new paths are discovered. 34 | pathRefreshInterval = 5 * time.Minute 35 | // pathRefreshLeadTime specifies when a refresh is triggered for a 36 | // path, relative to its expiry. 37 | pathRefreshLeadTime = 2 * time.Minute 38 | // pathPruneLeadTime specifies when, relative to its expiry, a path 39 | // that is no longer returned from a path query is dropped from the cache. 40 | pathPruneLeadTime = pathRefreshMinInterval 41 | 42 | pathDownNotificationTimeout = 10 * time.Second 43 | pathDownNotificationChannelCapacity = 8 44 | 45 | defaultSelectorMaxReplyPaths = 4 46 | 47 | statsNumLatencySamples = 4 48 | ) 49 | 50 | // maxTime is the maximum usable time value (https://stackoverflow.com/a/32620397) 51 | var maxTime = time.Unix(1<<63-62135596801, 999999999) 52 | var maxDuration = time.Duration(1<<63 - 1) 53 | -------------------------------------------------------------------------------- /pkg/pan/dns_txt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 pan 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "net" 22 | "strings" 23 | ) 24 | 25 | type dnsResolver struct { 26 | res dnsTXTResolver 27 | } 28 | 29 | type dnsTXTResolver interface { 30 | LookupTXT(context.Context, string) ([]string, error) 31 | } 32 | 33 | var _ resolver = &dnsResolver{} 34 | 35 | const scionAddrTXTTag = "scion=" 36 | 37 | // Resolve the name via DNS to return one scionAddr or an error. 38 | func (d *dnsResolver) Resolve(ctx context.Context, name string) (saddr scionAddr, err error) { 39 | addresses, err := d.queryTXTRecord(ctx, name) 40 | if err != nil { 41 | return scionAddr{}, err 42 | } 43 | var perr error 44 | for _, addr := range addresses { 45 | saddr, perr = parseSCIONAddr(addr) 46 | if perr == nil { 47 | return saddr, nil 48 | } 49 | } 50 | return scionAddr{}, fmt.Errorf("error parsing TXT SCION address records: %w", perr) 51 | } 52 | 53 | // queryTXTRecord queries the DNS for DNS TXT record(s) specifying the SCION address(es) for host. 54 | // Returns either at least one address, or else an error, of type HostNotFoundError if no matching record was found. 55 | func (d *dnsResolver) queryTXTRecord(ctx context.Context, host string) (addresses []string, err error) { 56 | if d.res == nil { 57 | return addresses, fmt.Errorf("invalid DNS resolver: %v", d.res) 58 | } 59 | if !strings.HasSuffix(host, ".") { 60 | host += "." 61 | } 62 | txtRecords, err := d.res.LookupTXT(ctx, host) 63 | var errDNSError *net.DNSError 64 | if errors.As(err, &errDNSError) { 65 | if errDNSError.IsNotFound { 66 | return addresses, HostNotFoundError{Host: host} 67 | } 68 | } 69 | if err != nil { 70 | return addresses, err 71 | } 72 | for _, txt := range txtRecords { 73 | if strings.HasPrefix(txt, scionAddrTXTTag) { 74 | addresses = append(addresses, strings.TrimPrefix(txt, scionAddrTXTTag)) 75 | } 76 | } 77 | if len(addresses) == 0 { 78 | return addresses, HostNotFoundError{Host: host} 79 | } 80 | return addresses, nil 81 | } 82 | -------------------------------------------------------------------------------- /pkg/pan/dns_txt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 pan 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestDNSResolver(t *testing.T) { 26 | cases := []struct { 27 | name string 28 | assertErr assert.ErrorAssertionFunc 29 | expected scionAddr 30 | }{ 31 | {"example.com", assert.NoError, mustParse("1-ff00:0:f00,[192.0.2.1]")}, 32 | {"example.net", assert.NoError, mustParse("1-ff00:0:ba5,[192.0.2.38]")}, 33 | {"example.org", assert.Error, scionAddr{}}, 34 | {"noia.example.org", assert.Error, scionAddr{}}, 35 | {"noip.example.org", assert.Error, scionAddr{}}, 36 | {"trailing.example.org", assert.Error, scionAddr{}}, 37 | {"example.edu", assertErrHostNotFound, scionAddr{}}, 38 | {"empty.example.edu", assertErrHostNotFound, scionAddr{}}, 39 | {"dummy4", assertErrHostNotFound, scionAddr{}}, 40 | {"barbaz", assertErrHostNotFound, scionAddr{}}, 41 | } 42 | var m mockResolver 43 | resolver := &dnsResolver{res: &m} 44 | for _, c := range cases { 45 | actual, err := resolver.Resolve(context.TODO(), c.name) 46 | if !c.assertErr(t, err) { 47 | continue 48 | } 49 | assert.Equal(t, c.expected, actual) 50 | } 51 | } 52 | 53 | func TestDNSResolverInvalid(t *testing.T) { 54 | r := &dnsResolver{res: nil} 55 | _, err := r.Resolve(context.TODO(), "example.com") 56 | assert.Error(t, err) 57 | } 58 | 59 | type mockResolver struct { 60 | net.Resolver 61 | } 62 | 63 | // LookupTXT mocks requesting the DNS TXT records for the given domain name. 64 | func (r *mockResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 65 | v, ok := map[string][]string{ 66 | "example.com.": { 67 | "NS=ns74430548", 68 | "doodle-site-verification=t4SRCkhsSDk_Ec9BPAr4xWQvYqoYJSLuoMmWLBdKqS0", 69 | "scion=1-ff00:0:f00,[192.0.2.1]", 70 | }, 71 | "example.net.": { 72 | "scion=1-ff00:0:ba5,[192.0.2.38]", 73 | "BOOM_verify_3ovqPKkST76TzF2c7b13YA", 74 | "v=spf1 include:_id.example.net ip4:192.0.2.38 ip4:192.0.2.197 ~all", 75 | }, 76 | "example.org.": { 77 | "scion=1-ff00:0:invalid,[192.0.2.1]", 78 | }, 79 | "noip.example.org.": { 80 | "scion=1-ff00:0:f01", 81 | }, 82 | "noia.example.org.": { 83 | "scion=192.0.2.100", 84 | }, 85 | "trailing.example.org.": { 86 | "scion=1-ff00:0:f01,[192.0.2.100] something more", 87 | }, 88 | "example.edu.": { 89 | "v=spf1 include:_id.example.org ip4:192.0.2.203 ip4:192.0.2.213 ~all", 90 | }, 91 | "empty.example.edu.": { 92 | "", 93 | }, 94 | }[name] 95 | if !ok { 96 | return nil, &net.DNSError{IsNotFound: true} 97 | } 98 | return v, nil 99 | } 100 | -------------------------------------------------------------------------------- /pkg/pan/flag.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "net/netip" 21 | "strconv" 22 | ) 23 | 24 | // IPPortValue implements the flag.Value for a net/netip.AddrPort, 25 | // using the ParseAddrPort function. 26 | type IPPortValue netip.AddrPort 27 | 28 | func (v *IPPortValue) Get() netip.AddrPort { 29 | return netip.AddrPort(*v) 30 | } 31 | 32 | func (v *IPPortValue) Set(s string) error { 33 | val, err := ParseOptionalIPPort(s) 34 | *v = IPPortValue(val) 35 | return err 36 | } 37 | 38 | func (v *IPPortValue) String() string { 39 | return netip.AddrPort(*v).String() 40 | } 41 | 42 | // ParseOptionalIPPort parses a string to netip.AddrPort 43 | // This accepts either of the following formats 44 | // 45 | // - : 46 | // - : 47 | // - (empty) 48 | // 49 | // This is provided by this package as typical usage of the Dial/Listen 50 | // will allow to provide the local address as a string, where the omitting 51 | // the IP is a convenient shortcut, valid for both IPv4 and IPv6. 52 | func ParseOptionalIPPort(s string) (netip.AddrPort, error) { 53 | if s == "" { 54 | return netip.AddrPort{}, nil 55 | } 56 | host, port, err := net.SplitHostPort(s) 57 | if err != nil { 58 | return netip.AddrPort{}, fmt.Errorf("unable to parse IP:Port (%q): %w", s, err) 59 | } 60 | port16, err := strconv.ParseUint(port, 10, 16) 61 | if err != nil { 62 | return netip.AddrPort{}, fmt.Errorf("invalid port %q parsing %q", port, s) 63 | } 64 | var ip netip.Addr 65 | if host != "" { 66 | ip, err = netip.ParseAddr(host) 67 | if err != nil { 68 | return netip.AddrPort{}, err 69 | } 70 | } 71 | return netip.AddrPortFrom(ip, uint16(port16)), nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/pan/flag_test.go: -------------------------------------------------------------------------------- 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 pan_test 16 | 17 | import ( 18 | "net/netip" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | 23 | "github.com/netsec-ethz/scion-apps/pkg/pan" 24 | ) 25 | 26 | func TestParseOptionalIPPort(t *testing.T) { 27 | cases := []struct { 28 | name string 29 | input string 30 | assertErr assert.ErrorAssertionFunc 31 | expected netip.AddrPort 32 | }{ 33 | { 34 | name: "empty", 35 | input: "", 36 | assertErr: assert.NoError, 37 | expected: netip.AddrPort{}, 38 | }, 39 | { 40 | name: "port 0", 41 | input: ":0", 42 | assertErr: assert.NoError, 43 | expected: netip.AddrPort{}, 44 | }, 45 | { 46 | name: "port", 47 | input: ":8888", 48 | assertErr: assert.NoError, 49 | expected: netip.AddrPortFrom(netip.Addr{}, 8888), 50 | }, 51 | { 52 | name: "ipv4 and port", 53 | input: "127.0.0.1:8888", 54 | assertErr: assert.NoError, 55 | expected: netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), 8888), 56 | }, 57 | { 58 | name: "ipv6 and port", 59 | input: "[::1]:8888", 60 | assertErr: assert.NoError, 61 | expected: netip.AddrPortFrom(netip.MustParseAddr("::1"), 8888), 62 | }, 63 | { 64 | name: "ipv4 only", 65 | input: "127.0.0.1", 66 | assertErr: assert.Error, 67 | }, 68 | { 69 | name: "ipv6 only", 70 | input: "::1", 71 | assertErr: assert.Error, 72 | }, 73 | } 74 | for _, c := range cases { 75 | t.Run(c.name, func(t *testing.T) { 76 | actual, err := pan.ParseOptionalIPPort(c.input) 77 | if !c.assertErr(t, err) { 78 | return 79 | } 80 | assert.Equal(t, c.expected, actual) 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pkg/pan/hosts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 pan 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "net" 22 | "strconv" 23 | ) 24 | 25 | var ( 26 | resolveEtcHosts resolver = &hostsfileResolver{"/etc/hosts"} 27 | resolveEtcScionHosts resolver = &hostsfileResolver{"/etc/scion/hosts"} 28 | resolveRains resolver = nil 29 | resolveDNSTxt resolver = &dnsResolver{net.DefaultResolver} 30 | ) 31 | 32 | // resolveUDPAddrAt parses the address and resolves the hostname. 33 | // The address can be of the form of a SCION address (i.e. of the form "ISD-AS,[IP]:port") 34 | // or in the form of "hostname:port". 35 | // If the address is in the form of a hostname, resolver is used to resolve the name. 36 | func resolveUDPAddrAt(ctx context.Context, address string, resolver resolver) (UDPAddr, error) { 37 | raddr, err := ParseUDPAddr(address) 38 | if err == nil { 39 | return raddr, nil 40 | } 41 | hostStr, portStr, err := net.SplitHostPort(address) 42 | if err != nil { 43 | return UDPAddr{}, err 44 | } 45 | port, err := strconv.ParseUint(portStr, 10, 16) 46 | if err != nil { 47 | return UDPAddr{}, err 48 | } 49 | host, err := resolver.Resolve(ctx, hostStr) 50 | if err != nil { 51 | return UDPAddr{}, err 52 | } 53 | return host.WithPort(uint16(port)), nil 54 | } 55 | 56 | // defaultResolver returns the default name resolver, used in ResolveUDPAddr. 57 | // It will use the following sources, in the given order of precedence, to 58 | // resolve a name: 59 | // 60 | // - /etc/hosts 61 | // - /etc/scion/hosts 62 | // - RAINS, if a server is configured in /etc/scion/rains.cfg. Disabled if built with !norains. 63 | // - DNS TXT records using the local DNS resolver (depending on OS config, see "Name Resolution" in net package docs) 64 | func defaultResolver() resolver { 65 | return resolverList{ 66 | resolveEtcHosts, 67 | resolveEtcScionHosts, 68 | resolveRains, 69 | resolveDNSTxt, 70 | } 71 | } 72 | 73 | // resolver is the interface to resolve a host name to a SCION host address. 74 | // Currently, this is implemented for reading the system hosts file, a SCION specific hosts file, 75 | // RAINS, and DNS TXT records for SCION of the format "scion=ia,ip" 76 | type resolver interface { 77 | // Resolve finds an address for the name. 78 | // Returns a HostNotFoundError if the name was not found, but otherwise no 79 | // error occurred. 80 | Resolve(ctx context.Context, name string) (scionAddr, error) 81 | } 82 | 83 | // resolverList represents a list of Resolvers that are processed in sequence 84 | // to return the first match. 85 | type resolverList []resolver 86 | 87 | func (resolvers resolverList) Resolve(ctx context.Context, name string) (scionAddr, error) { 88 | var errHostNotFound HostNotFoundError 89 | var rerr error 90 | for _, resolver := range resolvers { 91 | if resolver == nil { 92 | // skip RAINS resolver when disabled 93 | continue 94 | } 95 | // check ctx to avoid unnecessary calls with already expired context 96 | if err := ctx.Err(); err != nil { 97 | rerr = err 98 | break 99 | } 100 | addr, err := resolver.Resolve(ctx, name) 101 | if err == nil { 102 | return addr, nil 103 | } else if !errors.As(err, &errHostNotFound) { 104 | // do not directly fail on first resolver error 105 | rerr = err 106 | } 107 | } 108 | if rerr != nil { 109 | // fmt.Fprintf(os.Stderr, "pan library: resolver error: %w", rerr) 110 | return scionAddr{}, fmt.Errorf("pan library: resolver error: %w", rerr) 111 | } 112 | return scionAddr{}, HostNotFoundError{name} 113 | } 114 | -------------------------------------------------------------------------------- /pkg/pan/hosts_test_file: -------------------------------------------------------------------------------- 1 | # regular IPv4 hosts 2 | 127.0.0.1 localhost 3 | 123.456.789.0 dummy1 4 | 5 | # regular IPv6 hosts 6 | fe80:cd00:0:cde:1257:0:211e:729c dummy2 7 | 123:4567:89ab:cdef:123:4567:89ab:cdef dummy3 8 | 9 | # SCION hosts 10 | #17-ffaa:0:0,[10.0.8.15] commented 11 | 17-ffaa:0:1,[192.168.1.1] host1.1 host1.2 12 | 18-ffaa:1:2,[10.0.8.10] host2 # comment 13 | 17-ffaa:0:1,[192.168.1.1] host3 14 | 20-ffaa:c0ff:ee12,[0:0:0ff1:ce00:dead:10cc:baad:f00d] host4 15 | -------------------------------------------------------------------------------- /pkg/pan/hostsfile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 pan 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "fmt" 21 | "os" 22 | "strings" 23 | ) 24 | 25 | type hostsTable map[string]scionAddr 26 | 27 | // hostsfileResolver is an implementation of the resolver interface, backed 28 | // by an /etc/hosts-like file. 29 | type hostsfileResolver struct { 30 | path string 31 | } 32 | 33 | func (r *hostsfileResolver) Resolve(ctx context.Context, name string) (scionAddr, error) { 34 | // Note: obviously not perfectly elegant to parse the entire file for 35 | // every query. However, properly caching this and still always provide 36 | // fresh results after changes to the hosts file seems like a bigger task and 37 | // for now that would be overkill. 38 | table, err := loadHostsFile(r.path) 39 | if err != nil { 40 | return scionAddr{}, fmt.Errorf("error loading %s: %w", r.path, err) 41 | } 42 | addr, ok := table[name] 43 | if !ok { 44 | return scionAddr{}, HostNotFoundError{name} 45 | } 46 | return addr, nil 47 | } 48 | 49 | func loadHostsFile(path string) (hostsTable, error) { 50 | file, err := os.Open(path) 51 | if os.IsNotExist(err) { 52 | // not existing file treated like an empty file, 53 | // just return an empty table 54 | return hostsTable(nil), nil 55 | } else if err != nil { 56 | return nil, err 57 | } 58 | defer file.Close() 59 | return parseHostsFile(file) 60 | } 61 | 62 | func parseHostsFile(file *os.File) (hostsTable, error) { 63 | hosts := make(hostsTable) 64 | scanner := bufio.NewScanner(file) 65 | for scanner.Scan() { 66 | line := scanner.Text() 67 | 68 | // ignore comments 69 | cstart := strings.IndexRune(line, '#') 70 | if cstart >= 0 { 71 | line = line[:cstart] 72 | } 73 | 74 | // cut into fields: address name1 name2 ... 75 | fields := strings.Fields(line) 76 | if len(fields) == 0 { 77 | continue 78 | } 79 | addr, err := parseSCIONAddr(fields[0]) 80 | if err != nil { 81 | continue 82 | } 83 | 84 | // map hostnames to scionAddress 85 | for _, name := range fields[1:] { 86 | hosts[name] = addr 87 | } 88 | } 89 | return hosts, scanner.Err() 90 | } 91 | -------------------------------------------------------------------------------- /pkg/pan/pan_test.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "errors" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestErrNoPathTo(t *testing.T) { 25 | ia := MustParseIA("1-ff00:0:1") 26 | err := errNoPathTo(ia) 27 | assert.Equal(t, err.Error(), "no path to 1-ff00:0:1") 28 | assert.True(t, errors.Is(err, ErrNoPath)) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/pan/quic_listen.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "net" 21 | "net/netip" 22 | 23 | "github.com/quic-go/quic-go" 24 | ) 25 | 26 | // QUICListener is a wrapper around a quic.Listener that also holds the underlying 27 | // net.PacketConn. This is necessary because quic.Listener does not expose the 28 | // underlying connection, which is needed to close it. 29 | type QUICListener struct { 30 | *quic.Listener 31 | Conn net.PacketConn 32 | } 33 | 34 | func (l *QUICListener) Close() error { 35 | err := l.Listener.Close() 36 | l.Conn.Close() 37 | return err 38 | } 39 | 40 | // ListenQUIC listens for QUIC connections on a SCION/UDP port. 41 | // 42 | // See note on wildcard addresses in the package documentation. 43 | func ListenQUIC( 44 | ctx context.Context, 45 | local netip.AddrPort, 46 | tlsConf *tls.Config, 47 | quicConfig *quic.Config, 48 | listenConnOptions ...ListenConnOptions, 49 | ) (*QUICListener, error) { 50 | 51 | conn, err := ListenUDP(ctx, local, listenConnOptions...) 52 | if err != nil { 53 | return nil, err 54 | } 55 | // HACK: we silence the log here to shut up quic-go's warning about trying to 56 | // set receive buffer size (it's not a UDPConn, we know). 57 | silenceLog() 58 | defer unsilenceLog() 59 | listener, err := quic.Listen(conn, tlsConf, quicConfig) 60 | if err != nil { 61 | conn.Close() 62 | return nil, err 63 | } 64 | return &QUICListener{Listener: listener, Conn: conn}, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/pan/rains.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | //go:build !norains 16 | // +build !norains 17 | 18 | package pan 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "net" 24 | "net/netip" 25 | "os" 26 | "strings" 27 | "time" 28 | 29 | "github.com/netsec-ethz/rains/pkg/rains" 30 | "github.com/scionproto/scion/pkg/addr" 31 | "github.com/scionproto/scion/pkg/snet" 32 | ) 33 | 34 | const rainsConfigPath = "/etc/scion/rains.cfg" 35 | 36 | func init() { 37 | resolveRains = &rainsResolver{} 38 | } 39 | 40 | type rainsResolver struct{} 41 | 42 | var _ resolver = &rainsResolver{} 43 | 44 | func (r *rainsResolver) Resolve(ctx context.Context, name string) (scionAddr, error) { 45 | server, err := readRainsConfig() 46 | if err != nil { 47 | return scionAddr{}, err 48 | } 49 | if server.Port == 0 { 50 | // nobody to ask, so we won't get a reply 51 | return scionAddr{}, HostNotFoundError{name} 52 | } 53 | return rainsQuery(ctx, server, name) 54 | } 55 | 56 | func readRainsConfig() (UDPAddr, error) { 57 | bs, err := os.ReadFile(rainsConfigPath) 58 | if os.IsNotExist(err) { 59 | return UDPAddr{}, nil 60 | } else if err != nil { 61 | return UDPAddr{}, fmt.Errorf("error loading %s: %w", rainsConfigPath, err) 62 | } 63 | address, err := ParseUDPAddr(strings.TrimSpace(string(bs))) 64 | if err != nil { 65 | return UDPAddr{}, fmt.Errorf("error parsing %s, expected SCION UDP address: %w", rainsConfigPath, err) 66 | } 67 | return address, nil 68 | } 69 | 70 | func rainsQuery(ctx context.Context, server UDPAddr, hostname string) (scionAddr, error) { 71 | const ( 72 | rainsCtx = "." // use global context 73 | qType = rains.OTScionAddr // request SCION addresses 74 | expire = 5 * time.Minute // sensible expiry date? 75 | timeout = 500 * time.Millisecond // timeout for query 76 | ) 77 | qOpts := []rains.Option{} // no options 78 | 79 | // TODO(chaehni): This call can sometimes cause a timeout even though the server is reachable (see issue #221) 80 | // The (default) timeout value has been decreased to counter this behavior until the problem is resolved. 81 | srv := &snet.UDPAddr{ 82 | IA: addr.IA(server.IA), 83 | Host: net.UDPAddrFromAddrPort(netip.AddrPortFrom(server.IP, server.Port)), 84 | } 85 | 86 | reply, err := rainsQueryChecked(ctx, hostname, rainsCtx, []rains.Type{qType}, qOpts, expire, timeout, srv) 87 | if err != nil { 88 | return scionAddr{}, err 89 | } 90 | addrStr, ok := reply[qType] 91 | if !ok { 92 | return scionAddr{}, &HostNotFoundError{hostname} 93 | } 94 | addr, err := parseSCIONAddr(addrStr) 95 | if err != nil { 96 | return scionAddr{}, fmt.Errorf("address for host %q invalid: %w", hostname, err) 97 | } 98 | return addr, nil 99 | } 100 | 101 | func rainsQueryChecked(ctx context.Context, name, rainsCtx string, types []rains.Type, opts []rains.Option, 102 | expire, timeout time.Duration, addr net.Addr) (res map[rains.Type]string, err error) { 103 | 104 | var contextTimeout time.Duration 105 | deadline, finite := ctx.Deadline() 106 | if finite { 107 | contextTimeout = time.Until(deadline) 108 | if contextTimeout < 0 { 109 | return res, context.DeadlineExceeded 110 | } 111 | } else { 112 | contextTimeout = timeout 113 | } 114 | 115 | done := make(chan struct{}) 116 | go func() { 117 | defer close(done) 118 | res, err = rains.Query(name, rainsCtx, types, opts, expire, contextTimeout, addr) 119 | }() 120 | select { 121 | case <-ctx.Done(): 122 | return res, ctx.Err() 123 | case <-done: 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /pkg/pan/silence.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "io" 19 | "log" 20 | "sync" 21 | ) 22 | 23 | var logSilencerMutex sync.Mutex 24 | var logSilencerCount int32 25 | var logSilencerOriginal io.Writer 26 | 27 | // silenceLog redirects the log.Default writer to a black hole. 28 | // It can be reenabled by calling unsilenceLog. 29 | // These functions can safely be called from multiple goroutines concurrently; 30 | // the log will remain silenced until unsilenceLog was called for each 31 | // silenceLog call. 32 | func silenceLog() { 33 | logSilencerMutex.Lock() 34 | defer logSilencerMutex.Unlock() 35 | 36 | logSilencerCount++ 37 | if logSilencerCount == 1 { 38 | logSilencerOriginal = log.Default().Writer() 39 | log.Default().SetOutput(io.Discard) 40 | } 41 | } 42 | 43 | func unsilenceLog() { 44 | logSilencerMutex.Lock() 45 | defer logSilencerMutex.Unlock() 46 | 47 | logSilencerCount-- 48 | if logSilencerCount == 0 { 49 | log.Default().SetOutput(logSilencerOriginal) 50 | logSilencerOriginal = nil 51 | } else if logSilencerCount < 0 { 52 | panic("unsilenceLog called more often than silenceLog") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/pan/udp_listen_test.go: -------------------------------------------------------------------------------- 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 pan 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestPathsMRU(t *testing.T) { 24 | const maxSize = 3 25 | cases := []struct { 26 | name string 27 | before []PathFingerprint 28 | insert PathFingerprint 29 | after []PathFingerprint 30 | }{ 31 | { 32 | name: "nil", 33 | before: nil, 34 | insert: "a", 35 | after: []PathFingerprint{"a"}, 36 | }, 37 | { 38 | name: "empty", 39 | before: []PathFingerprint{}, 40 | insert: "a", 41 | after: []PathFingerprint{"a"}, 42 | }, 43 | { 44 | name: "new, not full", 45 | before: []PathFingerprint{"a", "b"}, 46 | insert: "c", 47 | after: []PathFingerprint{"c", "a", "b"}, 48 | }, 49 | { 50 | name: "existing, not full", 51 | before: []PathFingerprint{"a", "b"}, 52 | insert: "b", 53 | after: []PathFingerprint{"b", "a"}, 54 | }, 55 | { 56 | name: "new, full", 57 | before: []PathFingerprint{"a", "b", "c"}, 58 | insert: "d", 59 | after: []PathFingerprint{"d", "a", "b"}, 60 | }, 61 | { 62 | name: "existing, full, first", 63 | before: []PathFingerprint{"a", "b", "c"}, 64 | insert: "a", 65 | after: []PathFingerprint{"a", "b", "c"}, 66 | }, 67 | { 68 | name: "existing, full, middle", 69 | before: []PathFingerprint{"a", "b", "c"}, 70 | insert: "b", 71 | after: []PathFingerprint{"b", "a", "c"}, 72 | }, 73 | { 74 | name: "existing, full, last", 75 | before: []PathFingerprint{"a", "b", "c"}, 76 | insert: "c", 77 | after: []PathFingerprint{"c", "a", "b"}, 78 | }, 79 | } 80 | for _, c := range cases { 81 | t.Run(c.name, func(t *testing.T) { 82 | paths := testdataPathsFromFingerprints(c.before) 83 | l := pathsMRU(paths) 84 | l.insert(&Path{Fingerprint: c.insert}, maxSize) 85 | actual := fingerprintsFromTestdataPaths(l) 86 | assert.Equal(t, c.after, actual) 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /pkg/quicutil/tls.go: -------------------------------------------------------------------------------- 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 quicutil 16 | 17 | import ( 18 | "bytes" 19 | "crypto/rand" 20 | "crypto/rsa" 21 | "crypto/tls" 22 | "crypto/x509" 23 | "crypto/x509/pkix" 24 | "encoding/pem" 25 | "fmt" 26 | "math/big" 27 | "time" 28 | ) 29 | 30 | // MustGenerateSelfSignedCert generates private key and a self-signed dummy 31 | // certificate usable for TLS with InsecureSkipVerify: true. 32 | // Like GenerateSelfSignedCert but panics on error and returns a slice with a 33 | // single entry, for convenience when initializing a tls.Config structure. 34 | func MustGenerateSelfSignedCert() []tls.Certificate { 35 | cert, err := GenerateSelfSignedCert() 36 | if err != nil { 37 | panic(err) 38 | } 39 | return []tls.Certificate{*cert} 40 | } 41 | 42 | // GenerateSelfSignedCert generates a private key and a self-signed dummy 43 | // certificate usable for TLS with InsecureSkipVerify: true 44 | func GenerateSelfSignedCert() (*tls.Certificate, error) { 45 | priv, err := rsaGenerateKey() 46 | if err != nil { 47 | return nil, err 48 | } 49 | return createCertificate(priv) 50 | } 51 | 52 | func rsaGenerateKey() (*rsa.PrivateKey, error) { 53 | return rsa.GenerateKey(rand.Reader, 2048) 54 | } 55 | 56 | // createCertificate creates a self-signed dummy certificate for the given key 57 | // Inspired/copy pasted from crypto/tls/generate_cert.go 58 | func createCertificate(priv *rsa.PrivateKey) (*tls.Certificate, error) { 59 | notBefore := time.Now() 60 | notAfter := notBefore.Add(365 * 24 * time.Hour) 61 | 62 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 63 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to generate serial number: %w", err) 66 | } 67 | 68 | template := x509.Certificate{ 69 | SerialNumber: serialNumber, 70 | Subject: pkix.Name{ 71 | Organization: []string{"scionlab"}, 72 | }, 73 | NotBefore: notBefore, 74 | NotAfter: notAfter, 75 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 76 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 77 | BasicConstraintsValid: true, 78 | DNSNames: []string{"dummy"}, 79 | } 80 | 81 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | certPEMBuf := &bytes.Buffer{} 87 | if err := pem.Encode(certPEMBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 88 | return nil, err 89 | } 90 | 91 | privBytes, err := x509.MarshalPKCS8PrivateKey(priv) 92 | if err != nil { 93 | return nil, fmt.Errorf("unable to marshal private key: %w", err) 94 | } 95 | 96 | keyPEMBuf := &bytes.Buffer{} 97 | if err := pem.Encode(keyPEMBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { 98 | return nil, err 99 | } 100 | 101 | cert, err := tls.X509KeyPair(certPEMBuf.Bytes(), keyPEMBuf.Bytes()) 102 | return &cert, err 103 | } 104 | -------------------------------------------------------------------------------- /pkg/shttp/README.md: -------------------------------------------------------------------------------- 1 | # HTTP over SCION 2 | 3 | This package contains glue code to use the standard net/http libraries for HTTP 4 | over SCION. 5 | 6 | This uses a QUIC session with a single stream as a transport, instead of the 7 | standard TCP (for which we do not have an implementation on top of SCION). 8 | As TLS is always enabled in QUIC, we use an insecure TLS session with self 9 | signed certificates to get something similar to TCP for insecure HTTP. 10 | For HTTPS, we'll have two TLS sessions; the insecure TLS for the basic 11 | transport and on top of that, the "normal" TLS for the actual web content. 12 | This may seem silly, and the net/http library provides enough hooks that would 13 | allow using the "normal" TLS session directly. However, only this setup allows 14 | to implement CONNECT, e.g. to proxy HTTPS traffic over HTTP. 15 | 16 | ### Client 17 | 18 | We use the standard net/http Client/Transport with a customized Dial function: 19 | 20 | ```Go 21 | // Create a client with our Transport/Dialer: 22 | client := &http.Client{ 23 | Transport: shttp.DefaultTransport, 24 | } 25 | // Make requests as usual 26 | resp, err := client.Get("http://server:8080/download") 27 | ``` 28 | 29 | Hostnames are resolved by parsing the `/etc/hosts` file or by a RAINS lookup 30 | (see [Hostnames](../../README.md#Hostnames)). 31 | URLs potentially containing raw SCION addresses must be *mangled* before 32 | passing into the client (or any other place where they might be parsed as URL). 33 | ```Go 34 | resp, err := client.Get(shttp.MangleSCIONURL("http://1-ff00:0:110,127.0.0.1:8080/download")) 35 | ``` 36 | 37 | ### Server 38 | 39 | The server is used just like the standard net/http server; the handlers work 40 | all the same, only a custom listener is used for serving. 41 | 42 | Example: 43 | ```Go 44 | handler := http.FileServer(http.Dir("/usr/share/doc")))) 45 | log.Fatal(shttp.ListenAndServe(":80", handler)) 46 | ``` 47 | -------------------------------------------------------------------------------- /pkg/shttp/server.go: -------------------------------------------------------------------------------- 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 shttp 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "net" 21 | "net/http" 22 | 23 | "github.com/netsec-ethz/scion-apps/pkg/pan" 24 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 25 | ) 26 | 27 | // Server wraps a http.Server making it work with SCION 28 | type Server struct { 29 | *http.Server 30 | } 31 | 32 | // ListenAndServe listens for HTTP connections on the SCION address addr and calls Serve 33 | // with handler to handle requests 34 | func ListenAndServe(addr string, handler http.Handler) error { 35 | s := &Server{ 36 | Server: &http.Server{ 37 | Addr: addr, 38 | Handler: handler, 39 | }, 40 | } 41 | return s.ListenAndServe() 42 | } 43 | 44 | // ListenAndServe listens for HTTPS connections on the SCION address addr and calls Serve 45 | // with handler to handle requests 46 | func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler) error { 47 | s := &Server{ 48 | Server: &http.Server{ 49 | Addr: addr, 50 | Handler: handler, 51 | }, 52 | } 53 | return s.ListenAndServeTLS(certFile, keyFile) 54 | } 55 | 56 | func (srv *Server) Serve(l net.Listener) error { 57 | // Providing a custom listener defeats the purpose of this library. 58 | panic("not implemented") 59 | } 60 | 61 | func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error { 62 | // Providing a custom listener defeats the purpose of this library. 63 | panic("not implemented") 64 | } 65 | 66 | // ListenAndServe listens for QUIC connections on srv.Addr and 67 | // calls Serve to handle incoming requests 68 | func (srv *Server) ListenAndServe() error { 69 | listener, err := listen(srv.Addr) 70 | if err != nil { 71 | return err 72 | } 73 | defer listener.Close() 74 | return srv.Server.Serve(listener) 75 | } 76 | 77 | func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { 78 | listener, err := listen(srv.Addr) 79 | if err != nil { 80 | return err 81 | } 82 | defer listener.Close() 83 | return srv.Server.ServeTLS(listener, certFile, keyFile) 84 | } 85 | 86 | func listen(addr string) (net.Listener, error) { 87 | tlsCfg := &tls.Config{ 88 | NextProtos: []string{quicutil.SingleStreamProto}, 89 | Certificates: quicutil.MustGenerateSelfSignedCert(), 90 | } 91 | laddr, err := pan.ParseOptionalIPPort(addr) 92 | if err != nil { 93 | return nil, err 94 | } 95 | quicListener, err := pan.ListenQUIC(context.Background(), laddr, tlsCfg, nil) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return quicutil.SingleStreamListener{QUICListener: quicListener}, nil 100 | } 101 | -------------------------------------------------------------------------------- /pkg/shttp3/README.md: -------------------------------------------------------------------------------- 1 | # HTTP/3 over SCION 2 | 3 | This package contains glue code to use the quic-go/http3 libraries for HTTP/3 4 | over SCION. 5 | 6 | Usage of this package is analogous to pkg/shttp, and thus analogous to 7 | using the net/http standard library. 8 | -------------------------------------------------------------------------------- /pkg/shttp3/server.go: -------------------------------------------------------------------------------- 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 shttp3 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "net" 21 | "net/http" 22 | 23 | "github.com/quic-go/quic-go/http3" 24 | 25 | "github.com/netsec-ethz/scion-apps/pkg/pan" 26 | ) 27 | 28 | // Server wraps a http3.Server making it work with SCION 29 | type Server struct { 30 | *http3.Server 31 | } 32 | 33 | // ListenAndServe listens on the SCION/UDP address addr and calls the handler 34 | // for HTTP/3 requests on incoming connections. http.DefaultServeMux is used 35 | // when handler is nil. 36 | func ListenAndServe(addr string, certFile, keyFile string, handler http.Handler) error { 37 | var err error 38 | certs := make([]tls.Certificate, 1) 39 | certs[0], err = tls.LoadX509KeyPair(certFile, keyFile) 40 | if err != nil { 41 | return err 42 | } 43 | s := &Server{ 44 | Server: &http3.Server{ 45 | Addr: addr, 46 | Handler: handler, 47 | TLSConfig: &tls.Config{ 48 | Certificates: certs, 49 | }, 50 | }, 51 | } 52 | return s.ListenAndServe() 53 | } 54 | 55 | // ListenAndServe listens on the UDP address s.Addr and calls s.Handler to 56 | // handle HTTP/3 requests on incoming connections. 57 | func (s *Server) ListenAndServe() error { 58 | laddr, err := pan.ParseOptionalIPPort(s.Addr) 59 | if err != nil { 60 | return err 61 | } 62 | sconn, err := pan.ListenUDP(context.Background(), laddr) 63 | if err != nil { 64 | return err 65 | } 66 | return s.Server.Serve(sconn) 67 | } 68 | 69 | func (s *Server) Serve(conn net.PacketConn) error { 70 | // Providing a custom packet conn defeats the purpose of this library. 71 | panic("not implemented") 72 | } 73 | -------------------------------------------------------------------------------- /pkg/shttp3/transport.go: -------------------------------------------------------------------------------- 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 shttp3 provides glue to use quic-go/http3 libraries for HTTP/3 over 16 | // SCION. 17 | package shttp3 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "net/netip" 23 | 24 | "github.com/quic-go/quic-go" 25 | "github.com/quic-go/quic-go/http3" 26 | 27 | "github.com/netsec-ethz/scion-apps/pkg/pan" 28 | ) 29 | 30 | // DefaultTransport is the default RoundTripper that can be used for HTTP/3 31 | // over SCION. 32 | var DefaultTransport = &http3.RoundTripper{ 33 | Dial: (&Dialer{ 34 | Policy: nil, 35 | }).Dial, 36 | } 37 | 38 | // Dialer dials a QUIC connection over SCION. 39 | // This is the Dialer used for shttp3.DefaultTransport. 40 | type Dialer struct { 41 | Local netip.AddrPort 42 | Policy pan.Policy 43 | sessions []*pan.QUICEarlySession 44 | } 45 | 46 | // Dial dials a QUIC connection over SCION. 47 | func (d *Dialer) Dial(ctx context.Context, addr string, tlsCfg *tls.Config, 48 | cfg *quic.Config) (quic.EarlyConnection, error) { 49 | 50 | remote, err := pan.ResolveUDPAddr(ctx, addr) 51 | if err != nil { 52 | return nil, err 53 | } 54 | session, err := pan.DialQUICEarly(ctx, d.Local, remote, addr, tlsCfg, cfg, pan.WithPolicy(d.Policy)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | d.sessions = append(d.sessions, session) 59 | return session, nil 60 | } 61 | 62 | func (d *Dialer) SetPolicy(policy pan.Policy) { 63 | d.Policy = policy 64 | for _, s := range d.sessions { 65 | s.Conn.SetPolicy(policy) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sensorapp/sensorapp_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | //go:build integration 16 | // +build integration 17 | 18 | package main 19 | 20 | import ( 21 | "testing" 22 | "time" 23 | 24 | "github.com/netsec-ethz/scion-apps/pkg/integration" 25 | ) 26 | 27 | const ( 28 | clientBin = "scion-sensorfetcher" 29 | serverBin = "scion-sensorserver" 30 | ) 31 | 32 | func TestMain(m *testing.M) { 33 | integration.TestMain(m) 34 | } 35 | 36 | func TestIntegrationSensorserver(t *testing.T) { 37 | // Server 38 | serverPortOffset := 42003 39 | serverArgs := []string{"-p", integration.ServerPortReplace} 40 | 41 | scriptServerWithInput := integration.InputPipeScript( 42 | t.TempDir(), 43 | "sensorserver", 44 | "sensorserver/timereader.py", 45 | integration.AppBinPath(serverBin), 46 | ) 47 | 48 | // Client 49 | clientCmd := integration.AppBinPath(clientBin) 50 | clientArgs := []string{"-s", integration.DstAddrPattern + ":" + integration.ServerPortReplace} 51 | 52 | in := integration.NewAppsIntegration(clientCmd, scriptServerWithInput, clientArgs, serverArgs) 53 | in.ClientOutMatch = integration.RegExp(`^20\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}` + "\n") 54 | in.ClientDelay = 250 * time.Millisecond 55 | 56 | iaPairs := integration.DefaultIAPairs() 57 | // Add different ports to servers. 58 | integration.AssignUniquePorts(iaPairs, serverPortOffset, 1) 59 | if err := in.Run(t, iaPairs); err != nil { 60 | t.Error(err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sensorapp/sensorfetcher/sensorfetcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | // sensorfetcher application 16 | // For documentation on how to setup and run the application see: 17 | // https://github.com/netsec-ethz/scion-apps/blob/master/README.md 18 | package main 19 | 20 | import ( 21 | "context" 22 | "flag" 23 | "fmt" 24 | "log" 25 | "net/netip" 26 | "os" 27 | "strings" 28 | 29 | "github.com/netsec-ethz/scion-apps/pkg/pan" 30 | ) 31 | 32 | func check(e error) { 33 | if e != nil { 34 | log.Fatal(e) 35 | } 36 | } 37 | 38 | func main() { 39 | serverAddrStr := flag.String("s", "", "Server address ( or )") 40 | interactive := flag.Bool("i", false, "Interactive path selection, prompt to choose path") 41 | sequence := flag.String("sequence", "", "Sequence of space separated hop predicates to specify path") 42 | preference := flag.String("preference", "", "Preference sorting order for paths. "+ 43 | "Comma-separated list of available sorting options: "+ 44 | strings.Join(pan.AvailablePreferencePolicies, "|")) 45 | 46 | flag.Parse() 47 | 48 | if len(*serverAddrStr) == 0 { 49 | flag.Usage() 50 | os.Exit(2) 51 | } 52 | 53 | policy, err := pan.PolicyFromCommandline(*sequence, *preference, *interactive) 54 | check(err) 55 | serverAddr, err := pan.ResolveUDPAddr(context.TODO(), *serverAddrStr) 56 | check(err) 57 | conn, err := pan.DialUDP(context.Background(), netip.AddrPort{}, serverAddr, pan.WithPolicy(policy)) 58 | check(err) 59 | 60 | receivePacketBuffer := make([]byte, 2500) 61 | sendPacketBuffer := make([]byte, 0) 62 | 63 | _, err = conn.Write(sendPacketBuffer) 64 | check(err) 65 | 66 | n, err := conn.Read(receivePacketBuffer) 67 | check(err) 68 | 69 | fmt.Print(string(receivePacketBuffer[:n])) 70 | } 71 | -------------------------------------------------------------------------------- /sensorapp/sensorserver/sensorreader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | HOST = "localhost" 5 | PORT = 4223 6 | CO2_UID = "CW1" 7 | TEMPERATURE_UID = "zFn" 8 | SOUND_INTENSITY_UID = "B59" 9 | DUST_UID = "Bt3" 10 | AMBIENTLIGHT_UID = "yFZ" 11 | UVLIGHT_UID = "xn8" 12 | MASTERBRICK1_UID = "5W4zM3" 13 | MASTERBRICK2_UID = "6JMWng" 14 | PIEZOSPEAKER_UID = "C8k" 15 | MOTIONDETECTOR_UID = "BRA" 16 | HUMIDITY_UID = "CXa" 17 | 18 | from tinkerforge.ip_connection import IPConnection 19 | from tinkerforge.bricklet_co2 import BrickletCO2 20 | from tinkerforge.bricklet_sound_intensity import SoundIntensity 21 | from tinkerforge.bricklet_dust_detector import DustDetector 22 | from tinkerforge.bricklet_temperature import Temperature 23 | from tinkerforge.bricklet_ambient_light import AmbientLight 24 | from tinkerforge.bricklet_uv_light import BrickletUVLight 25 | from tinkerforge.bricklet_motion_detector import BrickletMotionDetector 26 | from tinkerforge.bricklet_humidity_v2 import HumidityV2 27 | 28 | import time 29 | from datetime import datetime 30 | 31 | if __name__ == "__main__": 32 | ipcon = IPConnection() # Create IP connection 33 | co2 = BrickletCO2(CO2_UID, ipcon) # Create device object 34 | humidity = HumidityV2(HUMIDITY_UID, ipcon) 35 | sound_intensity = SoundIntensity(SOUND_INTENSITY_UID, ipcon) 36 | dust_density = DustDetector( DUST_UID, ipcon) 37 | temperature = Temperature( TEMPERATURE_UID, ipcon) 38 | ambientlight = AmbientLight( AMBIENTLIGHT_UID, ipcon) 39 | uvlight = BrickletUVLight( UVLIGHT_UID, ipcon) 40 | motiondetect = BrickletMotionDetector( MOTIONDETECTOR_UID, ipcon) 41 | 42 | ipcon.connect(HOST, PORT) # Connect to brickd 43 | 44 | while (True): 45 | curtime = datetime.now() 46 | print( "Time: " + curtime.strftime('%Y/%m/%d %H:%M:%S')) 47 | 48 | motion = motiondetect.get_motion_detected() 49 | print( "Motion: " + str( motion )) 50 | 51 | illuminance = ambientlight.get_illuminance()/10.0 52 | print( "Illuminance: " + str(illuminance)) 53 | 54 | uv_light = uvlight.get_uv_light() 55 | print( "UV Light: " + str(uv_light)) 56 | 57 | # Get current CO2 concentration (unit is ppm) 58 | cur_co2_concentration = co2.get_co2_concentration() 59 | print( "CO2: " + str(cur_co2_concentration)) 60 | 61 | # Get current sound intensity level 62 | cur_si = sound_intensity.get_intensity() 63 | print( "Sound intensity: " + str(cur_si)) 64 | 65 | # Get current dust density 66 | cur_dd = dust_density.get_dust_density() 67 | print( "Dust density: " + str(cur_dd)) 68 | 69 | # Get current humidity level 70 | cur_humidity = humidity.get_humidity()/100.0 71 | print("Humidity: " + str(cur_humidity)) 72 | 73 | # Get temperature from humidity sensor 74 | cur_humidity = humidity.get_temperature()/100.0 75 | print("Temperature (Humidity sensor): " + str(cur_humidity)) 76 | 77 | # Temperature 78 | cur_temp = temperature.get_temperature()/100.00 79 | print( "Temperature: " + str(cur_temp), flush=True ) 80 | 81 | # Print out values every 10 seconds 82 | time.sleep(10) 83 | -------------------------------------------------------------------------------- /sensorapp/sensorserver/sensorserver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | // sensorserver application 16 | // For documentation on how to setup and run the application see: 17 | // https://github.com/netsec-ethz/scion-apps/blob/master/README.md 18 | package main 19 | 20 | import ( 21 | "bufio" 22 | "context" 23 | "flag" 24 | "log" 25 | "net/netip" 26 | "os" 27 | "strings" 28 | "sync" 29 | 30 | "github.com/netsec-ethz/scion-apps/pkg/pan" 31 | ) 32 | 33 | const ( 34 | timeString string = "Time" 35 | separatorString string = ": " 36 | timeAndSeparatorString string = timeString + separatorString 37 | ) 38 | 39 | func check(e error) { 40 | if e != nil { 41 | log.Fatal(e) 42 | } 43 | } 44 | 45 | var sensorData map[string]string 46 | var sensorDataLock sync.Mutex 47 | 48 | func init() { 49 | sensorData = make(map[string]string) 50 | } 51 | 52 | // Obtains input from sensor observation application 53 | func parseInput() { 54 | input := bufio.NewScanner(os.Stdin) 55 | for input.Scan() { 56 | line := input.Text() 57 | index := strings.Index(line, timeAndSeparatorString) 58 | if index == 0 { 59 | // We found a time string, format in case parsing is desired: 2017/11/16 21:29:49 60 | timestr := line[len(timeAndSeparatorString):] 61 | sensorDataLock.Lock() 62 | sensorData[timeString] = timestr 63 | sensorDataLock.Unlock() 64 | continue 65 | } 66 | index = strings.Index(line, separatorString) 67 | if index > 0 { 68 | sensorType := line[:index] 69 | sensorDataLock.Lock() 70 | sensorData[sensorType] = line 71 | sensorDataLock.Unlock() 72 | } 73 | } 74 | } 75 | 76 | func main() { 77 | go parseInput() 78 | 79 | // Fetch arguments from command line 80 | port := flag.Uint("p", 40002, "Server Port") 81 | flag.Parse() 82 | 83 | local := netip.AddrPortFrom(netip.Addr{}, uint16(*port)) 84 | conn, err := pan.ListenUDP(context.Background(), local) 85 | check(err) 86 | 87 | receivePacketBuffer := make([]byte, 2500) 88 | sendPacketBuffer := make([]byte, 2500) 89 | for { 90 | _, clientAddress, path, err := conn.ReadFromVia(receivePacketBuffer) 91 | check(err) 92 | 93 | // Packet received, send back response to same client 94 | var sensorValues string 95 | var timeStr string 96 | sensorDataLock.Lock() 97 | for k, v := range sensorData { 98 | if strings.Index(k, timeString) == 0 { 99 | timeStr = v 100 | } else { 101 | sensorValues = sensorValues + v + "\n" 102 | } 103 | } 104 | sensorDataLock.Unlock() 105 | sensorValues = timeStr + "\n" + sensorValues 106 | copy(sendPacketBuffer, sensorValues) 107 | 108 | _, err = conn.WriteToVia(sendPacketBuffer[:len(sensorValues)], clientAddress, path) 109 | check(err) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /sensorapp/sensorserver/timereader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | from datetime import datetime 6 | 7 | if __name__ == "__main__": 8 | while (True): 9 | curtime = datetime.now() 10 | print( "Time: " + curtime.strftime('%Y/%m/%d %H:%M:%S'), flush=True ) 11 | time.sleep(1) 12 | -------------------------------------------------------------------------------- /skip/README.md: -------------------------------------------------------------------------------- 1 | # skip 2 | 3 | **skip** (SCION kludge in *p*rowsers, also "ship" in many languages and so fitting 4 | with the lighthouse/beacon scheme :boat:) is a poor man's browser integration 5 | for SCION. 6 | 7 | skip uses a [Proxy auto-config](https://en.wikipedia.org/wiki/Proxy_auto-config) 8 | file to forward all requests with a SCION destination to a proxy server running 9 | as a (native) binary on localhost. 10 | This mechanism does not let us dynamically look up whether a name refers to 11 | a SCION address. We identify SCION addresses as either: 12 | * the host name of a SCION host from `/etc/hosts` or `/etc/scion/hosts` 13 | * a mangled SCION address in the form `--`, e.g. `http://17-ffaa_0_1101-129.132.121.164/` 15 | 16 | ## Installation 17 | 18 | * Build the `scion-skip` binary by running `make scion-skip` (see 19 | [Build](../README.md#build) in the main README). 20 | 21 | * Install the `skip.pac` as an "Automatic proxy configuration". 22 | 23 | In Firefox (currently v84.0), navigate to 24 | **Preferences** / **General** / **Network Settings**, enable "Automatic proxy 25 | configuration URL" and enter `http://localhost:8888/skip.pac`. 26 | Adapt the address if you're running skip on a non-default address with `--bind`. 27 | 28 | ## Usage 29 | 30 | This requires a running SCION endhost stack, i.e. a running SCION dispatcher 31 | and SCION daemon. Please refer to '[Running](../../README.md#Running)' in this 32 | repository's main README and the [SCIONLab tutorials](https://docs.scionlab.org) to get started. 33 | 34 | Start `bin/scion-skip` and keep it running in the background. 35 | 36 | Enter SCION addresses in the URL bar of your browser, mangled as described above: 37 | * [http://17-ffaa_0_1101-129.132.121.164/](http://17-ffaa_0_1101-129.132.121.164/) 38 | * [http://www.scionlab.org](http://www.scionlab.org), assuming there is an 39 | entry for `www.scionlab.org` in `/etc/scion/hosts`. 40 | 41 | ## Limitations 42 | 43 | Obviously this is not great, but hey, it's a start. Some inspiration for how to 44 | to build something more advanced can be found in this extensions for the gopher 45 | protocol, [OverbiteNX](https://github.com/classilla/overbitenx). 46 | -------------------------------------------------------------------------------- /skip/skip.pac: -------------------------------------------------------------------------------- 1 | const scionHosts = new Set([ 2 | {{range .SCIONHosts}} "{{.|js}}", 3 | {{end}}]) 4 | 5 | function FindProxyForURL(url, host) 6 | { 7 | let mungedScionAddr = /^\d+-[-_.\dA-Fa-f]+$/ 8 | if (host.match(mungedScionAddr) != null || 9 | scionHosts.has(host)) { 10 | return "PROXY {{.ProxyAddress|js}}"; 11 | } 12 | return "DIRECT"; 13 | } 14 | -------------------------------------------------------------------------------- /ssh/README.md: -------------------------------------------------------------------------------- 1 | # SCION enabled SSH 2 | 3 | SSH client and server running over SCION network. 4 | 5 | ### Dependencies 6 | 7 | Building the SSH client and server applications requires `libpam0g-dev`: 8 | 9 | ```shell 10 | sudo apt-get install -y libpam0g-dev 11 | ``` 12 | 13 | 14 | ### Usage 15 | 16 | SCION infrastructure has to be installed and running. Instructions can be found [here](https://netsec-ethz.github.io/scion-tutorials/) 17 | 18 | You'll need to create a client key (if you don't have one yet): 19 | ``` 20 | cd ~/.ssh 21 | ssh-keygen -t rsa -f id_rsa 22 | ``` 23 | 24 | And create an authorized key file for the server with the public key (note that you'd usually place this in `/home//.ssh/authorized_keys` whereas `` is the user on the server you want to gain access to, but make sure not to overwrite an existing file): 25 | ``` 26 | cd scion-apps/ssh/server 27 | cp ~/.ssh/id_rsa.pub ./authorized_keys 28 | ``` 29 | 30 | Running the server: 31 | ``` 32 | cd scion-apps/ssh/server 33 | # If you are not root, you need to use sudo. You might also need the -E flag to preserve environment variables. 34 | sudo -E ./server -oPort=2200 -oAuthorizedKeysFile=./authorized_keys 35 | # You might also want to disable password authentication for security reasons with -oPasswordAuthentication=no 36 | ``` 37 | 38 | 39 | Running the client: 40 | ``` 41 | cd scion-apps/ssh/client 42 | ./client -p 2200 1-ffaa:1:abc,[127.0.0.1] -oUser=username 43 | ``` 44 | 45 | Using SCP: 46 | ``` 47 | cd scion-apps/ssh/scp 48 | ./scp.sh -P 2200 localFileToCopy.txt [1-ffaa:1:abc,[127.0.0.1]]:remoteTarget.txt 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /ssh/client/clientconfig/clientconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 clientconfig 16 | 17 | // ClientConfig is a struct containing configuration for the client. 18 | type ClientConfig struct { 19 | User string `regex:".*"` 20 | HostAddress string `regex:"([-.\\da-zA-Z]+)|(\\d+-[\\d:A-Fa-f]+,\\[[^\\]]+\\])"` 21 | Port string `regex:"0*([0-5]?\\d{0,4}|6([0-4]\\d{3}|5([0-4]\\d{2}|5([0-2]\\d|3[0-5]))))"` 22 | PasswordAuthentication string `regex:"(yes|no)"` 23 | PubkeyAuthentication string `regex:"(yes|no)"` 24 | StrictHostKeyChecking string `regex:"(yes|no|ask)"` 25 | IdentityFile []string `regex:".*"` 26 | LocalForward string `regex:".*"` 27 | RemoteForward string `regex:".*"` 28 | UserKnownHostsFile string `regex:".*"` 29 | ProxyCommand string `regex:".*"` 30 | } 31 | 32 | // Create creates a new ClientConfig with the default values. 33 | func Create() *ClientConfig { 34 | return &ClientConfig{ 35 | User: "", 36 | HostAddress: "", 37 | Port: "22", 38 | PasswordAuthentication: "yes", 39 | PubkeyAuthentication: "yes", 40 | StrictHostKeyChecking: "ask", 41 | UserKnownHostsFile: "~/.ssh/known_hosts", 42 | IdentityFile: []string{ 43 | "~/.ssh/id_ed25519", 44 | "~/.ssh/id_ecdsa", 45 | "~/.ssh/id_dsa", 46 | "~/.ssh/id_rsa", 47 | "~/.ssh/identity", 48 | }, 49 | LocalForward: "", 50 | RemoteForward: "", 51 | ProxyCommand: "", 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ssh/client/clientconfig/clientconfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 clientconfig 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | 24 | "github.com/netsec-ethz/scion-apps/ssh/config" 25 | ) 26 | 27 | func TestDefaultConfig(t *testing.T) { 28 | Convey("Given an example SSH config file", t, func() { 29 | configString := ` 30 | Host * 31 | ForwardAgent no 32 | ForwardX11 no 33 | ForwardX11Trusted yes 34 | RhostsRSAAuthentication no 35 | RSAAuthentication yes 36 | PasswordAuthentication no 37 | HostbasedAuthentication yes 38 | GSSAPIAuthentication no 39 | GSSAPIDelegateCredentials no 40 | GSSAPIKeyExchange no 41 | GSSAPITrustDNS no 42 | BatchMode no 43 | CheckHostIP yes 44 | AddressFamily any 45 | ConnectTimeout 0 46 | StrictHostKeyChecking no 47 | IdentityFile ~/.ssh/identity 48 | IdentityFile ~/.ssh/id_rsa 49 | IdentityFile ~/.ssh/id_dsa 50 | IdentityFile ~/.ssh/id_ecdsa 51 | IdentityFile ~/.ssh/id_ed25519 52 | Port 65535 53 | Protocol 2 54 | Cipher 3des 55 | Ciphers aes128-ctr,aes192-ctr,aes256-ctr$ 56 | MACs hmac-md5,hmac-sha1,umac-64@openssh.$ 57 | EscapeChar ~ 58 | Tunnel no 59 | TunnelDevice any:any 60 | PermitLocalCommand no 61 | VisualHostKey no 62 | ProxyCommand ssh -q -W %h:%p gateway.exa$ 63 | RekeyLimit 1G 1h 64 | SendEnv LANG LC_* 65 | HashKnownHosts yes 66 | GSSAPIAuthentication yes 67 | GSSAPIDelegateCredentials no 68 | ` 69 | 70 | Convey("The new values are read correctly", func() { 71 | conf := &ClientConfig{} 72 | err := config.UpdateFromReader(conf, strings.NewReader(configString)) 73 | So(err, ShouldBeNil) 74 | So(conf.HostAddress, ShouldEqual, "") 75 | So(conf.PasswordAuthentication, ShouldEqual, "no") 76 | So(conf.StrictHostKeyChecking, ShouldEqual, "no") 77 | So(conf.Port, ShouldEqual, "65535") 78 | So(conf.IdentityFile[len(conf.IdentityFile)-1], ShouldEqual, "~/.ssh/identity") 79 | So(conf.IdentityFile[len(conf.IdentityFile)-2], ShouldEqual, "~/.ssh/id_rsa") 80 | So(conf.IdentityFile[len(conf.IdentityFile)-3], ShouldEqual, "~/.ssh/id_dsa") 81 | So(conf.IdentityFile[len(conf.IdentityFile)-4], ShouldEqual, "~/.ssh/id_ecdsa") 82 | So(conf.IdentityFile[len(conf.IdentityFile)-5], ShouldEqual, "~/.ssh/id_ed25519") 83 | }) 84 | 85 | }) 86 | } 87 | 88 | func TestPortRegex(t *testing.T) { 89 | Convey("Given a default config file", t, func() { 90 | conf := &ClientConfig{} 91 | 92 | Convey("Valid port numbers are accepted", func() { 93 | nums := []int{1, 2, 3, 10, 100, 1000, 10000, 45, 652, 3486, 43621, 6554, 66, 65535} 94 | for _, i := range nums { 95 | err := config.Set(conf, "Port", i) 96 | So(err, ShouldEqual, nil) 97 | So(conf.Port, ShouldEqual, fmt.Sprintf("%v", i)) 98 | } 99 | }) 100 | 101 | Convey("Invalid port numbers are not accepted", func() { 102 | nums := []int{-1, 2, 3, 351000, 10064300, 455635, 65345632, 34845636, 6436554, 65536, 70000} 103 | for _, i := range nums { 104 | if i >= 0 && i <= 65535 { 105 | continue 106 | } 107 | initialPort := conf.Port 108 | err := config.Set(conf, "Port", i) 109 | So(err, ShouldNotEqual, nil) 110 | So(conf.Port, ShouldEqual, fmt.Sprintf("%v", initialPort)) 111 | } 112 | }) 113 | 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /ssh/client/ssh/scion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 ssh 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "net/netip" 21 | "time" 22 | 23 | "github.com/quic-go/quic-go" 24 | "golang.org/x/crypto/ssh" 25 | 26 | "github.com/netsec-ethz/scion-apps/pkg/pan" 27 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 28 | ) 29 | 30 | // dialSCION starts a client connection to the given SSH server over SCION using QUIC. 31 | func dialSCION(ctx context.Context, 32 | addr string, 33 | policy pan.Policy, 34 | selector string, 35 | config *ssh.ClientConfig) (*ssh.Client, error) { 36 | 37 | remote, err := pan.ResolveUDPAddr(ctx, addr) 38 | if err != nil { 39 | return nil, err 40 | } 41 | sel, err := selectorByName(selector) 42 | if err != nil { 43 | return nil, err 44 | } 45 | tlsConf := &tls.Config{ 46 | NextProtos: []string{quicutil.SingleStreamProto}, 47 | InsecureSkipVerify: true, 48 | } 49 | quicConf := &quic.Config{ 50 | KeepAlivePeriod: 15 * time.Second, 51 | } 52 | sess, err := pan.DialQUIC(ctx, netip.AddrPort{}, remote, "", tlsConf, quicConf, pan.WithPolicy(policy), pan.WithSelector(sel)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | stream, err := quicutil.NewSingleStream(sess) 57 | if err != nil { 58 | return nil, err 59 | } 60 | conn, nc, rc, err := ssh.NewClientConn(stream, addr, config) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return ssh.NewClient(conn, nc, rc), nil 65 | } 66 | 67 | // tunnelDialSCION creates a tunnel using the given SSH client. 68 | func tunnelDialSCION(client *ssh.Client, addr string) (ssh.Channel, error) { 69 | openChannelData := directSCIONData{ 70 | addr: addr, 71 | } 72 | 73 | c, requests, err := client.OpenChannel("direct-scionquic", ssh.Marshal(&openChannelData)) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | go ssh.DiscardRequests(requests) 79 | return c, nil 80 | } 81 | 82 | type directSCIONData struct { 83 | addr string 84 | } 85 | -------------------------------------------------------------------------------- /ssh/client/ssh/selector.go: -------------------------------------------------------------------------------- 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 ssh 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "math/rand" 21 | "sync" 22 | "time" 23 | 24 | "github.com/netsec-ethz/scion-apps/pkg/pan" 25 | ) 26 | 27 | var ( 28 | AvailablePathSelectors = []string{"default", "ping", "round-robin", "random"} 29 | ) 30 | 31 | func selectorByName(name string) (pan.Selector, error) { 32 | switch name { 33 | case "default": 34 | return nil, nil 35 | case "ping": 36 | // Set Pinging Selector with active probing on four paths 37 | // Note: this would ideally be configurable 38 | selector := &pan.PingingSelector{ 39 | Interval: 2 * time.Second, 40 | Timeout: time.Second, 41 | } 42 | selector.SetActive(4) 43 | return selector, nil 44 | case "round-robin": 45 | return &roundRobinSelector{}, nil 46 | case "random": 47 | return &randomSelector{}, nil 48 | default: 49 | return nil, errors.New("unknown path selection option") 50 | } 51 | } 52 | 53 | type roundRobinSelector struct { 54 | mutex sync.Mutex 55 | paths []*pan.Path 56 | current int 57 | } 58 | 59 | func (s *roundRobinSelector) Path(_ context.Context) *pan.Path { 60 | s.mutex.Lock() 61 | defer s.mutex.Unlock() 62 | 63 | if len(s.paths) == 0 { 64 | return nil 65 | } 66 | p := s.paths[s.current] 67 | s.current = (s.current + 1) % len(s.paths) 68 | return p 69 | } 70 | 71 | func (s *roundRobinSelector) Initialize(local, remote pan.UDPAddr, paths []*pan.Path) { 72 | s.mutex.Lock() 73 | defer s.mutex.Unlock() 74 | s.paths = paths 75 | s.current = 0 76 | } 77 | 78 | func (s *roundRobinSelector) Refresh(paths []*pan.Path) { 79 | s.mutex.Lock() 80 | defer s.mutex.Unlock() 81 | s.paths = paths 82 | s.current = 0 // just start at the beginning again 83 | } 84 | 85 | func (s *roundRobinSelector) PathDown(pf pan.PathFingerprint, pi pan.PathInterface) { 86 | // ignore dead paths, just send on these anyway 87 | } 88 | 89 | func (s *roundRobinSelector) Close() error { 90 | return nil 91 | } 92 | 93 | type randomSelector struct { 94 | mutex sync.Mutex 95 | paths []*pan.Path 96 | } 97 | 98 | func (s *randomSelector) Path(_ context.Context) *pan.Path { 99 | s.mutex.Lock() 100 | defer s.mutex.Unlock() 101 | 102 | if len(s.paths) == 0 { 103 | return nil 104 | } 105 | r := rand.Intn(len(s.paths)) 106 | p := s.paths[r] 107 | return p 108 | } 109 | 110 | func (s *randomSelector) Initialize(local, remote pan.UDPAddr, paths []*pan.Path) { 111 | s.mutex.Lock() 112 | defer s.mutex.Unlock() 113 | s.paths = paths 114 | } 115 | 116 | func (s *randomSelector) Refresh(paths []*pan.Path) { 117 | s.mutex.Lock() 118 | defer s.mutex.Unlock() 119 | s.paths = paths 120 | } 121 | 122 | func (s *randomSelector) PathDown(pf pan.PathFingerprint, pi pan.PathInterface) { 123 | // ignore dead paths, just send on these anyway 124 | } 125 | 126 | func (s *randomSelector) Close() error { 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /ssh/client/ssh/shell.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 ssh 16 | 17 | import ( 18 | "encoding/binary" 19 | "os" 20 | "os/signal" 21 | "syscall" 22 | 23 | "golang.org/x/crypto/ssh" 24 | "golang.org/x/term" 25 | ) 26 | 27 | // Shell opens a new Shell session on the server this Client is connected to. 28 | func (client *Client) Shell() error { 29 | var ( 30 | termWidth, termHeight = 80, 24 31 | ) 32 | 33 | client.session.Stdout = os.Stdout 34 | client.session.Stderr = os.Stderr 35 | client.session.Stdin = os.Stdin 36 | 37 | modes := ssh.TerminalModes{ 38 | ssh.ECHO: 1, 39 | } 40 | 41 | if fd := int(os.Stdin.Fd()); term.IsTerminal(fd) { 42 | oldState, err := term.MakeRaw(fd) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | defer func() { 48 | _ = term.Restore(fd, oldState) 49 | }() 50 | 51 | w, h, err := term.GetSize(fd) 52 | if err == nil { 53 | termWidth = w 54 | termHeight = h 55 | } 56 | } 57 | 58 | if err := client.session.RequestPty("xterm", termHeight, termWidth, modes); err != nil { 59 | return err 60 | } 61 | 62 | if err := client.session.Shell(); err != nil { 63 | return err 64 | } 65 | 66 | // monitor for sigwinch 67 | go monWinCh(client.session, os.Stdout.Fd()) 68 | 69 | if err := client.WaitSession(); err != nil { 70 | return err 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func monWinCh(session *ssh.Session, fd uintptr) { 77 | sigs := make(chan os.Signal, 1) 78 | 79 | signal.Notify(sigs, syscall.SIGWINCH) 80 | defer signal.Stop(sigs) 81 | 82 | // resize the tty if any signals received 83 | for range sigs { 84 | _, _ = session.SendRequest("window-change", false, termSize(fd)) 85 | } 86 | } 87 | 88 | func termSize(fd uintptr) []byte { 89 | size := make([]byte, 16) 90 | 91 | width, height, err := term.GetSize(int(fd)) 92 | if err != nil { 93 | binary.BigEndian.PutUint32(size, uint32(80)) 94 | binary.BigEndian.PutUint32(size[4:], uint32(24)) 95 | return size 96 | } 97 | 98 | binary.BigEndian.PutUint32(size, uint32(width)) 99 | binary.BigEndian.PutUint32(size[4:], uint32(height)) 100 | 101 | return size 102 | } 103 | -------------------------------------------------------------------------------- /ssh/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 config 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/smartystreets/goconvey/convey" 21 | ) 22 | 23 | func TestConfig(t *testing.T) { 24 | Convey("Given a config struct with string option A and []string option B", t, func() { 25 | myStruct := &(struct { 26 | A string `regex:"(abc|\\d*)"` 27 | B []string `regex:"[xyzesno]*"` 28 | }{ 29 | A: "1337", 30 | B: []string{"meme"}, 31 | }) 32 | 33 | Convey("They should have their default values", func() { 34 | So(myStruct.A, ShouldEqual, "1337") 35 | So(myStruct.B[0], ShouldEqual, "meme") 36 | }) 37 | 38 | Convey("We should be able to set legal values", func() { 39 | err := Set(myStruct, "A", "abc") 40 | So(err, ShouldEqual, nil) 41 | So(myStruct.A, ShouldEqual, "abc") 42 | 43 | err = UpdateFromString(myStruct, "A 000000001000") 44 | So(err, ShouldEqual, nil) 45 | So(myStruct.A, ShouldEqual, "000000001000") 46 | 47 | b, err := SetIfNot(myStruct, "B", true, false) 48 | So(err, ShouldEqual, nil) 49 | So(b, ShouldEqual, false) 50 | So(len(myStruct.B), ShouldEqual, 2) 51 | So(myStruct.B[1], ShouldEqual, "yes") 52 | 53 | b, err = SetIfNot(myStruct, "A", 8, 8) 54 | So(err, ShouldEqual, nil) 55 | So(b, ShouldEqual, true) 56 | So(myStruct.A, ShouldEqual, "000000001000") 57 | }) 58 | 59 | Convey("We should not be able to set illegal values", func() { 60 | err := Set(myStruct, "A", "abcd") 61 | So(err, ShouldNotEqual, nil) 62 | So(myStruct.A, ShouldEqual, "1337") 63 | 64 | err = UpdateFromString(myStruct, "A abc def") 65 | So(err, ShouldNotEqual, nil) 66 | So(myStruct.A, ShouldEqual, "1337") 67 | 68 | b, err := SetIfNot(myStruct, "B", 17, 18) 69 | So(err, ShouldNotEqual, nil) 70 | So(b, ShouldEqual, false) 71 | So(len(myStruct.B), ShouldEqual, 1) 72 | }) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /ssh/scp/scp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scp -S "${BASH_SOURCE%/*}/../client/client" $@ 4 | -------------------------------------------------------------------------------- /ssh/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | golog "log" 21 | "net/netip" 22 | "os" 23 | "strconv" 24 | 25 | log "github.com/inconshreveable/log15" 26 | "gopkg.in/alecthomas/kingpin.v2" 27 | 28 | "github.com/netsec-ethz/scion-apps/pkg/pan" 29 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 30 | "github.com/netsec-ethz/scion-apps/ssh/config" 31 | "github.com/netsec-ethz/scion-apps/ssh/server/serverconfig" 32 | "github.com/netsec-ethz/scion-apps/ssh/server/ssh" 33 | "github.com/netsec-ethz/scion-apps/ssh/utils" 34 | ) 35 | 36 | const ( 37 | version = "1.0" 38 | ) 39 | 40 | var ( 41 | // Connection 42 | options = kingpin.Flag("option", "Set an option").Short('o').Strings() 43 | 44 | // Configuration file 45 | configurationFile = kingpin.Flag("config-file", "SSH server configuration file").Short('f').Default("/etc/ssh/sshd_config").ExistingFile() 46 | ) 47 | 48 | func createConfig() *serverconfig.ServerConfig { 49 | conf := serverconfig.Create() 50 | 51 | updateConfigFromFile(conf, *configurationFile) 52 | 53 | for _, option := range *options { 54 | err := config.UpdateFromString(conf, option) 55 | if err != nil { 56 | log.Debug("Error updating config from --option flag: %v", err) 57 | } 58 | } 59 | 60 | // TODO: Set port from listening address 61 | // setConfIfNot(conf, "Port", *PORT, 0) 62 | 63 | return conf 64 | } 65 | 66 | func updateConfigFromFile(conf *serverconfig.ServerConfig, pth string) { 67 | err := config.UpdateFromFile(conf, utils.ParsePath(pth)) 68 | if err != nil { 69 | if !os.IsNotExist(err) { 70 | golog.Panicf("Error updating config from file %s: %v", pth, err) 71 | } 72 | } 73 | } 74 | 75 | func main() { 76 | kingpin.Parse() 77 | log.Debug("Starting SCION SSH server...") 78 | 79 | conf := createConfig() 80 | 81 | sshServer, err := ssh.Create(conf, version) 82 | if err != nil { 83 | golog.Panicf("Error creating ssh server: %v", err) 84 | } 85 | 86 | port, err := strconv.ParseUint(conf.Port, 10, 16) 87 | if err != nil { 88 | golog.Panicf("Can't parse port %v: %v", conf.Port, err) 89 | } 90 | log.Debug("Currently, ListenAddress.Port is ignored (only value from config taken)") 91 | 92 | local := netip.AddrPortFrom(netip.Addr{}, uint16(port)) 93 | tlsConf := &tls.Config{ 94 | Certificates: quicutil.MustGenerateSelfSignedCert(), 95 | NextProtos: []string{quicutil.SingleStreamProto}, 96 | } 97 | ql, err := pan.ListenQUIC(context.Background(), local, tlsConf, nil) 98 | if err != nil { 99 | golog.Panicf("Failed to listen (%v)", err) 100 | } 101 | listener := quicutil.SingleStreamListener{QUICListener: ql} 102 | 103 | log.Debug("Starting to wait for connections") 104 | for { 105 | conn, err := listener.Accept() 106 | if err != nil { 107 | golog.Fatalf("Failed to accept session: %v", err) 108 | } 109 | sshServer.HandleConnection(conn) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ssh/server/serverconfig/serverconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 serverconfig 16 | 17 | // ServerConfig is a struct containing configuration for the server. 18 | type ServerConfig struct { 19 | AuthorizedKeysFile string `regex:".*"` 20 | Port string `regex:"0*([0-5]?\\d{0,4}|6([0-4]\\d{3}|5([0-4]\\d{2}|5([0-2]\\d|3[0-5]))))"` 21 | PasswordAuthentication string `regex:"(yes|no)"` 22 | PubkeyAuthentication string `regex:"(yes|no)"` 23 | HostKey string `regex:".*"` 24 | MaxAuthTries string `regex:"[1-9]\\d*"` 25 | } 26 | 27 | // Create creates a new ServerConfig with the default values. 28 | func Create() *ServerConfig { 29 | return &ServerConfig{ 30 | AuthorizedKeysFile: ".ssh/authorized_keys", 31 | Port: "22", 32 | PasswordAuthentication: "yes", 33 | PubkeyAuthentication: "yes", 34 | HostKey: "/etc/ssh/ssh_host_key", 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ssh/server/ssh/authcallbacks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 ssh 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/msteinert/pam" 22 | "golang.org/x/crypto/ssh" 23 | ) 24 | 25 | // PasswordAuth authenticates the client using password authentication. 26 | func (s *Server) PasswordAuth(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 27 | t, err := pam.StartFunc("", c.User(), func(s pam.Style, msg string) (string, error) { 28 | switch s { 29 | case pam.PromptEchoOff: 30 | return string(pass), nil 31 | } 32 | return "", fmt.Errorf("unsupported message style") 33 | }) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | if err := t.Authenticate(0); err != nil { 39 | return nil, fmt.Errorf("authenticate: %w", err) 40 | } 41 | if err := t.AcctMgmt(0); err != nil { 42 | return nil, fmt.Errorf("authenticate: %w", err) 43 | } 44 | 45 | return &ssh.Permissions{ 46 | CriticalOptions: map[string]string{ 47 | "user": c.User(), 48 | }, 49 | }, nil 50 | } 51 | 52 | func loadAuthorizedKeys(file string) (map[string]bool, error) { 53 | authKeys := make(map[string]bool) 54 | 55 | authorizedKeysBytes, err := os.ReadFile(file) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | for len(authorizedKeysBytes) > 0 { 61 | pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | authKeys[string(pubKey.Marshal())] = true 67 | authorizedKeysBytes = rest 68 | } 69 | 70 | return authKeys, nil 71 | } 72 | 73 | // PublicKeyAuth authenticates the client using a public key. 74 | func (s *Server) PublicKeyAuth(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { 75 | authKeys, err := loadAuthorizedKeys(s.authorizedKeysFile) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed loading authorized files: %w", err) 78 | } 79 | 80 | if authKeys[string(pubKey.Marshal())] { 81 | return &ssh.Permissions{ 82 | CriticalOptions: map[string]string{ 83 | "user": c.User(), 84 | }, 85 | Extensions: map[string]string{ 86 | // Record the public key used for authentication 87 | "pubkey-fp": ssh.FingerprintSHA256(pubKey), 88 | }, 89 | }, nil 90 | } 91 | 92 | return nil, fmt.Errorf("unknown public key for %q", c.User()) 93 | } 94 | -------------------------------------------------------------------------------- /ssh/server/ssh/ssh.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 ssh 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "os" 21 | "strconv" 22 | 23 | log "github.com/inconshreveable/log15" 24 | "golang.org/x/crypto/ssh" 25 | 26 | "github.com/netsec-ethz/scion-apps/ssh/server/serverconfig" 27 | "github.com/netsec-ethz/scion-apps/ssh/utils" 28 | ) 29 | 30 | // ChannelHandlerFunction is a type for channel handlers, such as terminal sessions, tunnels, or X11 forwarding. 31 | type channelHandlerFunction func(perms *ssh.Permissions, newChannel ssh.NewChannel) error 32 | 33 | // Server is a struct containing information about SSH servers. 34 | type Server struct { 35 | authorizedKeysFile string 36 | 37 | configuration *ssh.ServerConfig 38 | 39 | channelHandlers map[string]channelHandlerFunction 40 | } 41 | 42 | // Create creates a new unconnected Server object. 43 | func Create(config *serverconfig.ServerConfig, version string) (*Server, error) { 44 | server := &Server{ 45 | authorizedKeysFile: config.AuthorizedKeysFile, 46 | channelHandlers: make(map[string]channelHandlerFunction), 47 | } 48 | 49 | maxAuthTries, _ := strconv.Atoi(config.MaxAuthTries) 50 | server.configuration = &ssh.ServerConfig{ 51 | PasswordCallback: server.PasswordAuth, 52 | PublicKeyCallback: server.PublicKeyAuth, 53 | MaxAuthTries: maxAuthTries, 54 | //ServerVersion: fmt.Sprintf("SCION-ssh-server-v%s", version), 55 | } 56 | 57 | privateBytes, err := os.ReadFile(utils.ParsePath(config.HostKey)) 58 | if err != nil { 59 | return nil, fmt.Errorf("failed loading private key: %w", err) 60 | } 61 | private, err := ssh.ParsePrivateKey(privateBytes) 62 | if err != nil { 63 | return nil, fmt.Errorf("failed parsing private key: %w", err) 64 | } 65 | server.configuration.AddHostKey(private) 66 | 67 | server.channelHandlers["session"] = handleSession 68 | server.channelHandlers["direct-tcpip"] = handleTCPTunnel 69 | server.channelHandlers["direct-scionquic"] = handleSCIONQUICTunnel 70 | 71 | return server, nil 72 | } 73 | 74 | func (s *Server) handleChannels(perms *ssh.Permissions, chans <-chan ssh.NewChannel) { 75 | // Service the incoming Channel channel in go routine 76 | for newChannel := range chans { 77 | go s.handleChannel(perms, newChannel) 78 | } 79 | } 80 | 81 | func (s *Server) handleChannel(perms *ssh.Permissions, newChannel ssh.NewChannel) { 82 | if handler, exists := s.channelHandlers[newChannel.ChannelType()]; exists { 83 | err := handler(perms, newChannel) 84 | if err != nil { 85 | log.Error("error handling channel", "type", newChannel.ChannelType(), "err", err) 86 | } 87 | } else { 88 | _ = newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType())) 89 | return 90 | } 91 | } 92 | 93 | // HandleConnection handles a client connection. 94 | func (s *Server) HandleConnection(conn net.Conn) { 95 | log.Debug("Handling new connection") 96 | sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.configuration) 97 | if err != nil { 98 | log.Error("Failed to create new connection", "error", err) 99 | conn.Close() 100 | } 101 | 102 | log.Debug("New SSH connection", "remoteAddress", sshConn.RemoteAddr(), "clientVersion", sshConn.ClientVersion()) 103 | // Discard all global out-of-band Requests 104 | go ssh.DiscardRequests(reqs) 105 | // Accept all channels 106 | s.handleChannels(sshConn.Permissions, chans) 107 | } 108 | -------------------------------------------------------------------------------- /ssh/server/ssh/tunnelchannel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 ssh 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "encoding/binary" 21 | "fmt" 22 | "io" 23 | "net" 24 | "net/netip" 25 | "sync" 26 | 27 | "golang.org/x/crypto/ssh" 28 | 29 | "github.com/netsec-ethz/scion-apps/pkg/pan" 30 | "github.com/netsec-ethz/scion-apps/pkg/quicutil" 31 | ) 32 | 33 | func handleTunnelForRemoteConnection(connection ssh.Channel, remoteConnection net.Conn) { 34 | // Prepare teardown function 35 | close := func() { 36 | connection.Close() 37 | remoteConnection.Close() 38 | } 39 | 40 | var once sync.Once 41 | go func() { 42 | _, _ = io.Copy(connection, remoteConnection) 43 | once.Do(close) 44 | }() 45 | go func() { 46 | _, _ = io.Copy(remoteConnection, connection) 47 | once.Do(close) 48 | }() 49 | } 50 | 51 | func handleTCPTunnel(perms *ssh.Permissions, newChannel ssh.NewChannel) error { 52 | extraData := newChannel.ExtraData() 53 | addressLen := binary.BigEndian.Uint32(extraData[0:4]) 54 | address := string(extraData[4 : addressLen+4]) 55 | port := binary.BigEndian.Uint32(extraData[addressLen+4 : addressLen+8]) 56 | 57 | connection, requests, err := newChannel.Accept() 58 | if err != nil { 59 | return fmt.Errorf("could not accept channel: %w", err) 60 | } 61 | 62 | go ssh.DiscardRequests(requests) 63 | 64 | remoteConnection, err := net.Dial("tcp", fmt.Sprintf("%s:%v", address, port)) 65 | if err != nil { 66 | return fmt.Errorf("could not open remote connection: %w", err) 67 | } 68 | 69 | handleTunnelForRemoteConnection(connection, remoteConnection) 70 | return nil 71 | } 72 | 73 | func handleSCIONQUICTunnel(perms *ssh.Permissions, newChannel ssh.NewChannel) error { 74 | extraData := newChannel.ExtraData() 75 | addressLen := binary.BigEndian.Uint32(extraData[0:4]) 76 | address := string(extraData[4 : addressLen+4]) 77 | 78 | connection, requests, err := newChannel.Accept() 79 | if err != nil { 80 | return fmt.Errorf("could not accept channel: %w", err) 81 | } 82 | 83 | go ssh.DiscardRequests(requests) 84 | 85 | ctx := context.Background() 86 | remote, err := pan.ResolveUDPAddr(context.TODO(), address) 87 | if err != nil { 88 | return fmt.Errorf("could not resolve remote address: %w", err) 89 | } 90 | tlsConf := &tls.Config{ 91 | NextProtos: []string{quicutil.SingleStreamProto}, 92 | InsecureSkipVerify: true, 93 | } 94 | sess, err := pan.DialQUIC(ctx, netip.AddrPort{}, remote, "", tlsConf, nil) 95 | if err != nil { 96 | return fmt.Errorf("could not open remote connection: %w", err) 97 | } 98 | remoteConnection, err := quicutil.NewSingleStream(sess) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | handleTunnelForRemoteConnection(connection, remoteConnection) 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /ssh/utils/path-utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 utils 16 | 17 | import ( 18 | "os/user" 19 | "path" 20 | "strings" 21 | ) 22 | 23 | // ParsePath performs tilde expansion, no-op if the path doesn't begin with a tilde. 24 | func ParsePath(pth string) string { 25 | home := "/" 26 | 27 | usr, err := user.Current() 28 | if err == nil { 29 | home = usr.HomeDir 30 | } 31 | 32 | if pth == "~" { 33 | return path.Join(home, pth[1:]) 34 | } else if strings.HasPrefix(pth, "~/") { 35 | return path.Join(home, pth[2:]) 36 | } else { 37 | return pth 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web-gateway/README.md: -------------------------------------------------------------------------------- 1 | # web-gateway 2 | web-gateway is a SCION web server that proxies web content from the TCP/IP web 3 | to the SCION web. 4 | 5 | ## Installation 6 | 7 | Build the `scion-web-gateway` binary by running `make scion-web-gateway` (see 8 | [Build](../README.md#build) in the main README). 9 | 10 | ## Usage 11 | 12 | This requires a running SCION endhost stack, i.e. a running SCION dispatcher 13 | and SCION daemon. Please refer to '[Running](../../README.md#Running)' in this 14 | repository's main README and the [SCIONLab tutorials](https://docs.scionlab.org) to get started. 15 | 16 | Start the gateway, telling it which TCP/IP hosts it should be mirroring: 17 | ``` 18 | bin/scion-ip-gateway scionlab.org www.scionlab.org www.scion-architecture.net 19 | ``` 20 | 21 | Note that the names we specify are individual host names, not domains, and 22 | every subdomain needs to be listed separately. 23 | 24 | While this server is running, you can access these websites over SCION, either 25 | using the [`scion-bat`](../bat/README.md) command line tool, or using the 26 | [`scion-skip`](../skip/README.md) browser integration. 27 | 28 | First, add a hostname entry for the mirrored hosts, pointing it to the SCION 29 | address that the gateway is running, e.g. add the following line to `/etc/scion/hosts`: 30 | ``` 31 | 17-ffaa:1:a,127.0.0.1 scionlab.org www.scionlab.org www.scion-architecture.net 32 | ``` 33 | 34 | Then simply access it: 35 | ``` 36 | bin/scion-bat http://www.scion-architecture.net/pages/publications/ 37 | bin/scion-bat https://www.scion-architecture.net/pages/publications/ 38 | ``` 39 | -------------------------------------------------------------------------------- /webapp/dependencies.md: -------------------------------------------------------------------------------- 1 | # Webapp Dependencies 2 | 3 | ## Package Default CL 4 | ```shell 5 | systemctl cat scion-webapp 6 | ``` 7 | 8 | ## Developer Default CL 9 | ```shell 10 | webapp -h 11 | ``` 12 | 13 | ## System Binaries Used 14 | - bash 15 | - echo 16 | - cd 17 | - sed 18 | - date 19 | - curl 20 | - set 21 | - grep 22 | - python3 23 | - df 24 | - tr 25 | - free 26 | - sleep 27 | - cat 28 | - systemctl 29 | 30 | ## SCION Binaries Used/Checked (should be somewhere on $PATH) 31 | - scion (for scion ping and scion traceroute) 32 | - scion-bwtestclient 33 | - scion-sensorfetcher 34 | 35 | ## Scripts/Directories Used/Checked 36 | - $SCION_ROOT/scion.sh (only for local topologies (ISD < 16)) 37 | - $sgen/.*topology.json (checking all subdirectories of $sgen for topology.json) 38 | - $sgen/.*(sciond|sd).toml (for reading sciond address) 39 | - $sgen/*cs*.toml (for reading prometheus server address) 40 | - $sgenc/*.crt 41 | - $sgenc/*.trc 42 | - $sgenc/ps[IA]-1.path.db 43 | 44 | ## Static Webserver Required 45 | - $srvroot/favico.ico 46 | - $srvroot/config/ 47 | - $srvroot/static/css/ 48 | - $srvroot/static/html/ 49 | - $srvroot/static/img/ 50 | - $srvroot/static/js/ 51 | - $srvroot/template/ 52 | - $srvroot/tests/health/ 53 | 54 | ## Static Webserver Generated 55 | - $srvroot/webapp.db 56 | - $srvroot/data/ 57 | - $srvroot/logs/ 58 | -------------------------------------------------------------------------------- /webapp/models/path/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 pathdb 16 | 17 | import ( 18 | "database/sql" 19 | ) 20 | 21 | // InitDB controls the opening connection to the database. 22 | func InitDB(filepath string) (*sql.DB, error) { 23 | //var err error 24 | db, err := sql.Open("sqlite3", filepath+"?mode=ro") 25 | if err != nil { 26 | return nil, err 27 | } 28 | err = db.Ping() 29 | if err != nil { 30 | return nil, err 31 | } 32 | return db, nil 33 | } 34 | 35 | // CloseDB will close the database, only use when app closes. 36 | func CloseDB(db *sql.DB) error { 37 | err := db.Close() 38 | return err 39 | } 40 | -------------------------------------------------------------------------------- /webapp/tests/asviz/config-d.json: -------------------------------------------------------------------------------- 1 | { 2 | "google_geolocation_apikey": null, 3 | "google_mapsjs_apikey": null, 4 | "nodes_xml_url": null, 5 | "labels_json_url": null 6 | } 7 | -------------------------------------------------------------------------------- /webapp/tests/asviz/geolocate-d.json: -------------------------------------------------------------------------------- 1 | { 2 | "location": { 3 | "lat": 0, 4 | "lng": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_as_topo.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "mtu": 1472, 5 | "group": 0, 6 | "is_core_as": false, 7 | "name": "1-ff00:0:112", 8 | "type": "root", 9 | "icon": "ISD-AS" 10 | }, 11 | { 12 | "group": 6, 13 | "addr": "[127.0.0.1]:2181", 14 | "icon": "ZOOKEEPER", 15 | "name": "zk-1", 16 | "type": "server" 17 | }, 18 | { 19 | "group": 3, 20 | "icon": "CERTIFICATE", 21 | "name": "cs1-ff00_0_112-1", 22 | "type": "server", 23 | "addr": "127.0.0.27", 24 | "port": 30050 25 | }, 26 | { 27 | "group": 4, 28 | "icon": "PATH", 29 | "name": "ps1-ff00_0_112-1", 30 | "type": "server", 31 | "addr": "127.0.0.28", 32 | "port": 30092 33 | }, 34 | { 35 | "group": 1, 36 | "icon": "BORDER", 37 | "name": "br1-ff00_0_112-1", 38 | "type": "router", 39 | "if_id": 66, 40 | "addr": "127.0.0.25", 41 | "port": 30095 42 | }, 43 | { 44 | "public port": 50000, 45 | "group": 0, 46 | "public addr": "127.0.0.7", 47 | "remote port": 50000, 48 | "type": "interface", 49 | "remote addr": "127.0.0.6", 50 | "icon": "ISD_AS", 51 | "mtu": 1472, 52 | "overlay": "UDP/IPv4", 53 | "to_if_id": 0, 54 | "name": "1-ff00:0:110", 55 | "bandwidth": 500, 56 | "link_type": "parent" 57 | }, 58 | { 59 | "group": 2, 60 | "icon": "BEACON", 61 | "name": "bs1-ff00_0_112-1", 62 | "type": "server", 63 | "addr": "127.0.0.26", 64 | "port": 30095 65 | } 66 | ], 67 | "links": [ 68 | { 69 | "source": "1-ff00:0:112", 70 | "target": "zk-1", 71 | "type": "as-in" 72 | }, 73 | { 74 | "source": "1-ff00:0:112", 75 | "target": "cs1-ff00_0_112-1", 76 | "type": "as-in" 77 | }, 78 | { 79 | "source": "1-ff00:0:112", 80 | "target": "ps1-ff00_0_112-1", 81 | "type": "as-in" 82 | }, 83 | { 84 | "source": "1-ff00:0:112", 85 | "target": "br1-ff00_0_112-1", 86 | "type": "as-in" 87 | }, 88 | { 89 | "source": "br1-ff00_0_112-1", 90 | "target": "1-ff00:0:110", 91 | "type": "as-parent" 92 | }, 93 | { 94 | "source": "1-ff00:0:112", 95 | "target": "bs1-ff00_0_112-1", 96 | "type": "as-in" 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_crt.html: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_path_topo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "a": "1-ff00:0:110", 4 | "ltype": "PARENT", 5 | "b": "1-ff00:0:112" 6 | }, 7 | { 8 | "a": "1-ff00:0:110", 9 | "ltype": "PARENT", 10 | "b": "1-ff00:0:111" 11 | }, 12 | { 13 | "a": "1-ff00:0:110", 14 | "ltype": "PEER", 15 | "b": "1-ff00:0:110" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "if_lists": [ 3 | { 4 | "interfaces": [ 5 | { 6 | "IFID": 66, 7 | "ISD": "1", 8 | "AS": "ff00:0:112" 9 | }, 10 | { 11 | "IFID": 59, 12 | "ISD": "1", 13 | "AS": "ff00:0:110" 14 | }, 15 | { 16 | "IFID": 24, 17 | "ISD": "1", 18 | "AS": "ff00:0:110" 19 | }, 20 | { 21 | "IFID": 26, 22 | "ISD": "1", 23 | "AS": "ff00:0:111" 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_seg_topo.json: -------------------------------------------------------------------------------- 1 | { 2 | "down_segments": { 3 | "if_lists": [ 4 | { 5 | "expTime": 1542761349, 6 | "timestamp": 1542739781, 7 | "interfaces": [ 8 | { 9 | "IFID": 24, 10 | "ISD": "1", 11 | "AS": "ff00:0:110" 12 | }, 13 | { 14 | "IFID": 26, 15 | "ISD": "1", 16 | "AS": "ff00:0:111" 17 | } 18 | ] 19 | } 20 | ] 21 | }, 22 | "core_segments": { 23 | "if_lists": [] 24 | }, 25 | "up_segments": { 26 | "if_lists": [ 27 | { 28 | "expTime": 1542761349, 29 | "timestamp": 1542739781, 30 | "interfaces": [ 31 | { 32 | "IFID": 59, 33 | "ISD": "1", 34 | "AS": "ff00:0:110" 35 | }, 36 | { 37 | "IFID": 66, 38 | "ISD": "1", 39 | "AS": "ff00:0:112" 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /webapp/tests/asviz/json_trc.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /webapp/tests/asviz/labels-d.json: -------------------------------------------------------------------------------- 1 | { 2 | "ISD": { 3 | "1": "TEST_ISD1", 4 | "2": "TEST_ISD2", 5 | "3": "TEST_ISD3", 6 | "4": "TEST_ISD4", 7 | "5": "TEST_ISD5" 8 | }, 9 | "AS": { 10 | "1-ff00:0:110": "old 1-11", 11 | "1-ff00:0:120": "old 1-12", 12 | "1-ff00:0:130": "old 1-13", 13 | "1-ff00:0:111": "old 1-14", 14 | "1-ff00:0:112": "old 1-17", 15 | "1-ff00:0:121": "old 1-15", 16 | "1-ff00:0:122": "old 1-18", 17 | "1-ff00:0:131": "old 1-16", 18 | "1-ff00:0:132": "old 1-19", 19 | "1-ff00:0:133": "old 1-10", 20 | "2-ff00:0:210": "old 2-21", 21 | "2-ff00:0:220": "old 2-22", 22 | "2-ff00:0:211": "old 2-23", 23 | "2-ff00:0:212": "old 2-25", 24 | "2-ff00:0:221": "old 2-24", 25 | "2-ff00:0:222": "old 2-26", 26 | "3-ff00:0:310": "old 3-1", 27 | "3-ff00:0:311": "old 3-2", 28 | "3-ff00:0:312": "old 3-3", 29 | "3-ff00:0:313": "old 3-4", 30 | "4-ff00:0:410": "old 4-1", 31 | "5-ff00:0:510": "old 5-1", 32 | "5-ff00:0:520": "old 5-2", 33 | "5-ff00:0:530": "old 5-3", 34 | "5-ff00:0:511": "old 5-4", 35 | "5-ff00:0:531": "old 5-5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webapp/tests/asviz/nodes-d.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /webapp/tests/asviz/path_info.html: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /webapp/tests/run_servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Build and run test servers to emulate test endpoints on localhost for webapp 3 | dstIA=1-ff00:0:112 4 | dstIP=127.0.0.2 5 | 6 | # test bwtest server 7 | echo "Running test bwtest server..." 8 | bwtestserver -s ${dstIA},[${dstIP}]:30100 -sciondFromIA & 9 | 10 | # test sensor server 11 | echo "Running test sensor server..." 12 | python3 ${GOPATH}/src/github.com/netsec-ethz/scion-apps/sensorapp/sensorserver/timereader.py | sensorserver -s ${dstIA},[${dstIP}]:42003 -sciondFromIA & 13 | 14 | # test scmp echo 15 | # dispatcher is responsible for responding to echo 16 | 17 | # test scmp traceroute 18 | # dispatcher is responsible for responding to traceroute 19 | 20 | # test pingpong server 21 | cd $SC 22 | echo "Running test pingpongserver..." 23 | ./bin/pingpong -mode server -local ${dstIA},[${dstIP}]:40002 -sciondFromIA & 24 | -------------------------------------------------------------------------------- /webapp/util/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 logs 16 | 17 | import ( 18 | log "github.com/inconshreveable/log15" 19 | ) 20 | 21 | // CheckError handles Error logging 22 | func CheckError(e error) bool { 23 | if e != nil { 24 | logError("Error:", "err", e) 25 | } 26 | return e != nil 27 | } 28 | 29 | // CheckFatal handles Fatal logging 30 | func CheckFatal(e error) bool { 31 | if e != nil { 32 | logFatal("Fatal:", "err", e) 33 | } 34 | return e != nil 35 | } 36 | 37 | func logError(msg string, a ...interface{}) { 38 | log.Error(msg, a...) 39 | } 40 | 41 | func logFatal(msg string, a ...interface{}) { 42 | log.Crit(msg, a...) 43 | } 44 | -------------------------------------------------------------------------------- /webapp/web/config/servers_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "bwtester": [ 3 | { 4 | "name": "17-ffaa:0:1102 ETHZ", 5 | "isdas": "17-ffaa:0:1102", 6 | "addr": "192.33.93.177", 7 | "port": 30100 8 | }, 9 | { 10 | "name": "17-ffaa:1:f", 11 | "isdas": "17-ffaa:1:f", 12 | "addr": "10.0.2.15", 13 | "port": 30100 14 | }, 15 | { 16 | "name": "19-ffaa:1:22", 17 | "isdas": "19-ffaa:1:22", 18 | "addr": "141.44.25.146", 19 | "port": 30100 20 | }, 21 | { 22 | "name": "17-ffaa:0:1107 Attachment Point", 23 | "isdas": "17-ffaa:0:1107", 24 | "addr": "10.0.8.1", 25 | "port": 30100 26 | }, 27 | { 28 | "name": "18-ffaa:0:1206 Attachment Point", 29 | "isdas": "18-ffaa:0:1206", 30 | "addr": "10.0.8.1", 31 | "port": 30100 32 | }, 33 | { 34 | "name": "19-ffaa:0:1303 Attachment Point", 35 | "isdas": "19-ffaa:0:1303", 36 | "addr": "10.0.8.1", 37 | "port": 30100 38 | }, 39 | { 40 | "name": "20-ffaa:0:1404 Attachment Point", 41 | "isdas": "20-ffaa:0:1404", 42 | "addr": "10.0.8.1", 43 | "port": 30100 44 | } 45 | ], 46 | "sensorapp": [ 47 | { 48 | "name": "17-ffaa:0:1102 ETHZ", 49 | "isdas": "17-ffaa:0:1102", 50 | "addr": "192.33.93.177", 51 | "port": 42003 52 | } 53 | ], 54 | "echo": [ 55 | { 56 | "name": "17-ffaa:0:1107 Attachment Point", 57 | "isdas": "17-ffaa:0:1107", 58 | "addr": "192.33.93.195", 59 | "port": 40002 60 | }, 61 | { 62 | "name": "18-ffaa:0:1206 Attachment Point", 63 | "isdas": "18-ffaa:0:1206", 64 | "addr": "128.237.153.120", 65 | "port": 40002 66 | }, 67 | { 68 | "name": "19-ffaa:0:1303 Attachment Point", 69 | "isdas": "19-ffaa:0:1303", 70 | "addr": "141.44.25.144", 71 | "port": 40002 72 | }, 73 | { 74 | "name": "20-ffaa:0:1404 Attachment Point", 75 | "isdas": "20-ffaa:0:1404", 76 | "addr": "203.230.60.98", 77 | "port": 40002 78 | } 79 | ], 80 | "traceroute": [ 81 | { 82 | "name": "17-ffaa:0:1107 Attachment Point", 83 | "isdas": "17-ffaa:0:1107", 84 | "addr": "192.33.93.195", 85 | "port": 40002 86 | }, 87 | { 88 | "name": "18-ffaa:0:1206 Attachment Point", 89 | "isdas": "18-ffaa:0:1206", 90 | "addr": "128.237.153.120", 91 | "port": 40002 92 | }, 93 | { 94 | "name": "19-ffaa:0:1303 Attachment Point", 95 | "isdas": "19-ffaa:0:1303", 96 | "addr": "141.44.25.144", 97 | "port": 40002 98 | }, 99 | { 100 | "name": "20-ffaa:0:1404 Attachment Point", 101 | "isdas": "20-ffaa:0:1404", 102 | "addr": "203.230.60.98", 103 | "port": 40002 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /webapp/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netsec-ethz/scion-apps/55667b489898af09ae9d8290410da0be176549f9/webapp/web/favicon.ico -------------------------------------------------------------------------------- /webapp/web/static/css/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 ETH Zurich 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @keyframes fadeout_err { 18 | 0% { 19 | opacity:1; 20 | } 21 | 100% { 22 | opacity:0; 23 | } 24 | } 25 | 26 | /* list open/close */ 27 | ul.tree li > a:not(:last-child):before { 28 | content: '+'; 29 | } 30 | 31 | ul.tree li.open > a:not(:last-child):before { 32 | content: '-'; 33 | } 34 | -------------------------------------------------------------------------------- /webapp/web/static/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 ETH Zurich 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* GENERAL */ 18 | #webapp { 19 | margin: 0 auto; 20 | width: 900px; 21 | /* font-size: 12px; */ 22 | } 23 | 24 | #error_text.enable { 25 | margin-top: 25px; 26 | text-align: center; 27 | color: #f00; 28 | animation: fadeout_err 10s; 29 | } 30 | 31 | svg.svg-ia { 32 | position: absolute; 33 | z-index: -1; 34 | } 35 | 36 | svg.svg-dials { 37 | width: 500px; 38 | position: absolute; 39 | z-index: -1; 40 | } 41 | 42 | .svg-ia circle { 43 | stroke: #666; 44 | stroke-width: 2px; 45 | } 46 | 47 | #svg-client circle { 48 | fill: #6f6; 49 | } 50 | 51 | #svg-cs line { 52 | stroke: #afa; 53 | } 54 | 55 | #cs-arrowhead { 56 | fill: #afa; 57 | } 58 | 59 | #svg-server circle { 60 | fill: #3cf; 61 | } 62 | 63 | #svg-sc line { 64 | stroke: #6ef; 65 | } 66 | 67 | #sc-arrowhead { 68 | fill: #6ef; 69 | } 70 | 71 | /* TABS GENERAL */ 72 | .column-center { 73 | float: left; 74 | width: 500px; 75 | height: 300px; 76 | font-size: 12px; 77 | } 78 | 79 | .row:after { 80 | content: ""; 81 | display: table; 82 | clear: both; 83 | overflow: scroll; 84 | } 85 | 86 | /* TAB ECHO */ 87 | #echo-continuous .chart { 88 | height: 200px; 89 | width: 698px; 90 | overflow: hidden; 91 | float: left; 92 | } 93 | 94 | /* TAB BWTESTER */ 95 | .bwtest-dial { 96 | width: 100px; 97 | display: inline-block; 98 | margin-top: 15px; 99 | } 100 | 101 | .bwtest-dials { 102 | height: 140px; 103 | width: 500px; 104 | margin: 0 auto; 105 | text-align: center; 106 | } 107 | 108 | .label-dial { 109 | text-align: center; 110 | width: 100px; 111 | font-weight: normal; 112 | } 113 | 114 | .pretty .state label:before { 115 | background-color: #fff; 116 | } 117 | 118 | #bwtest-continuous .chart { 119 | height: 200px; 120 | width: 349px; 121 | overflow: hidden; 122 | float: left; 123 | } 124 | 125 | /* CLIENT / SERVER */ 126 | .column-ia { 127 | float: left; 128 | width: 200px; 129 | height: 342px; 130 | font-size: 12px; 131 | } 132 | 133 | .scion-ia { 134 | width: 200px; 135 | height: 200px; 136 | text-align: center; 137 | display: table-cell; 138 | vertical-align: middle; 139 | } 140 | 141 | .title-ia { 142 | height: 42px; 143 | text-align: center; 144 | vertical-align: middle; 145 | line-height: 42px; 146 | } 147 | 148 | /* COMMAND OUTPUT */ 149 | .stdout { 150 | font-family: monospace; 151 | white-space: pre-line; 152 | height: 220px; 153 | overflow: auto; 154 | overflow-x: hidden; 155 | border-style: solid; 156 | border-radius: 5px; 157 | border-width: 2px; 158 | border-color: #666; 159 | background-color: #eee; 160 | margin-top: 5px; 161 | overflow-wrap: break-word; 162 | } 163 | 164 | /* TOOLTIPS */ 165 | [tooltip] { 166 | position: relative; 167 | } 168 | 169 | [tooltip]:before { 170 | border-bottom: 5px solid #333; 171 | border-left: 5px solid transparent; 172 | border-right: 5px solid transparent; 173 | content: ''; 174 | display: none; 175 | font-size: 0; 176 | height: 0; 177 | left: 35px; 178 | line-height: 0; 179 | position: absolute; 180 | top: 30px; 181 | width: 0; 182 | z-index: 8; 183 | } 184 | 185 | [tooltip]:after { 186 | background: #333; 187 | border-radius: 5px; 188 | color: #fff; 189 | content: attr(tooltip); 190 | display: none; 191 | height: 28px; 192 | left: 0px; 193 | line-height: 18px; 194 | padding: 5px 8px; 195 | position: absolute; 196 | top: 35px; 197 | white-space: nowrap; 198 | word-wrap: normal; 199 | z-index: 9; 200 | } 201 | 202 | [tooltip]:hover:before, [tooltip]:hover:after { 203 | display: block; 204 | } 205 | -------------------------------------------------------------------------------- /webapp/web/static/css/topology.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 ETH Zurich 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * --------------------------------- Cola Topology --------------------------------- 19 | */ 20 | 21 | .node:not (.host ) { 22 | stroke: white; 23 | stroke-width: 3px; 24 | } 25 | 26 | .node.host { 27 | stroke: #CCCCCC; 28 | stroke-width: 1px; 29 | } 30 | 31 | .marker{ 32 | fill: none; 33 | stroke: #666; 34 | stroke-opacity: .5; 35 | stroke-width: 1.5px; 36 | } 37 | 38 | .link { 39 | stroke-width: 4px; 40 | } 41 | 42 | .link.host { 43 | stroke: #444444; 44 | stroke-width: 1px; 45 | stroke-dasharray: 4, 4; 46 | } 47 | 48 | .group { 49 | stroke: white; 50 | stroke-width: 1.5px; 51 | cursor: move; 52 | opacity: 0.7; 53 | } 54 | 55 | .label { 56 | fill: black; 57 | text-anchor: middle; 58 | cursor: move; 59 | } 60 | 61 | .host.label { 62 | text-transform: capitalize; 63 | } 64 | 65 | .CORE.label { 66 | fill: white; 67 | } 68 | 69 | /** 70 | * --------------------------------- Additional Topology --------------------------------- 71 | */ 72 | 73 | .link.CORE { 74 | stroke-width: 5.5px; 75 | } 76 | 77 | .link.PEER { 78 | stroke-dasharray: 0, 2 1; 79 | } 80 | 81 | .link.SELECTED { 82 | stroke: red; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /webapp/web/static/img/lock-locked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapp/web/template/about.html: -------------------------------------------------------------------------------- 1 | {{define "about"}} {{template "header" .}} 2 | 3 |
4 | 5 |

SCIONLab About

6 | This Go web server wraps several SCION test client apps and provides an 7 | interface for any text and/or image output received. 8 |
9 | 10 | 11 |
12 | 20 | 21 |
22 | 23 | {{template "footer" .}} {{end}} 24 | -------------------------------------------------------------------------------- /webapp/web/template/astopo.html: -------------------------------------------------------------------------------- 1 | {{define "astopo"}} {{template "header" .}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 |

SCIONLab AS Monitor

14 |
15 |
16 |
17 |

AS {{.MyIA}} Topology Services

18 |
19 |

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 | 62 | 63 | {{template "footer" .}} {{end}} 64 | -------------------------------------------------------------------------------- /webapp/web/template/error.html: -------------------------------------------------------------------------------- 1 | {{define "error"}} {{template "header" .}} 2 | 3 |
I'm sorry, a page error occurred.
4 | 5 | {{template "footer" .}} {{end}} 6 | -------------------------------------------------------------------------------- /webapp/web/template/files.html: -------------------------------------------------------------------------------- 1 | {{define "dirview"}} {{template "header" .}} 2 | 3 |
4 | 5 | 20 | 21 |
22 | 23 | {{template "footer" .}} {{end}} 24 | -------------------------------------------------------------------------------- /webapp/web/template/footer.html: -------------------------------------------------------------------------------- 1 | {{define "footer"}} 2 | 14 | 16 | 17 | 18 | {{end}} 19 | -------------------------------------------------------------------------------- /webapp/web/template/trc.html: -------------------------------------------------------------------------------- 1 | {{define "trc"}} {{template "header" .}} 2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 |

SCIONLab Trust Roots

10 |
11 |
12 |
13 | .trc = Trust Root Configuration, .crt = AS Certificate 14 |
15 |
16 |
17 |
18 | 19 | 41 | 42 |
43 | 44 | {{template "footer" .}} {{end}} 45 | -------------------------------------------------------------------------------- /webapp/web/tests/health/beaconstest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i=0 3 | while [ $i -lt 3 ]; do 4 | # Query the cs metrics server 5 | response=$(curl --silent $4) 6 | if echo "$response" | grep "control_beaconing_registered_segments_total" | grep -q 'result="ok'; then 7 | exit 0 8 | fi 9 | i=$((i+1)) 10 | sleep 0.5 11 | done 12 | exit 1 13 | -------------------------------------------------------------------------------- /webapp/web/tests/health/bincheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # test will fail for non-zero exit and/or bytes in stderr 3 | 4 | # check for required binaries which may not have been built and installed 5 | missingbin=false 6 | declare -a apps=("scion" "scion-bwtestclient" "scion-sensorfetcher" ) 7 | for a in "${apps[@]}"; do 8 | path=$(which $a) 9 | if [ -x "$path" ]; then 10 | echo "$a exists" 11 | else 12 | echo "$a does not exist, check that SCION and SCION Apps are installed and that PATH is set correctly." 1>&2 13 | missingbin=true 14 | fi 15 | done 16 | 17 | if [ "$missingbin" = true ] ; then 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /webapp/web/tests/health/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "label": "netcheck", 5 | "script": "netcheck.sh", 6 | "desc": "Border router ICMP test" 7 | }, 8 | { 9 | "label": "timecheck", 10 | "script": "timecheck.sh", 11 | "desc": "Network time synchronization test" 12 | }, 13 | { 14 | "label": "checkdiskspace", 15 | "script": "testAvailDiskSpace.sh", 16 | "desc": "Available disk space tests" 17 | }, 18 | { 19 | "label": "checkmemory", 20 | "script": "testTotalMem.sh", 21 | "desc": "Total memory space tests" 22 | }, 23 | { 24 | "label": "checkVPN", 25 | "script": "testVPN.sh", 26 | "desc": "VPN configuration tests" 27 | }, 28 | { 29 | "label": "checkrunnngstatus", 30 | "script": "testSCIONRunning.sh", 31 | "desc": "SCION running test" 32 | }, 33 | { 34 | "label": "bincheck", 35 | "script": "bincheck.sh", 36 | "desc": "SCION apps test" 37 | }, 38 | { 39 | "label": "scmpcheck", 40 | "script": "scmpcheck.sh", 41 | "desc": "Border router SCMP test" 42 | }, 43 | { 44 | "label": "checkbeacons", 45 | "script": "beaconstest.sh", 46 | "desc": "Receiving beacons test" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /webapp/web/tests/health/netcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # test will fail for non-zero exit and/or bytes in stderr 3 | 4 | # error exit function 5 | error_exit() 6 | { 7 | echo "$1" 1>&2 8 | exit 1 9 | } 10 | 11 | topologyFile=$3 12 | # get remote addresses from interfaces 13 | ip_dsts=$(cat $topologyFile | python3 -c "import sys, json 14 | brs = json.load(sys.stdin)['border_routers'] 15 | for b in brs: 16 | for i in brs[b]['interfaces']: 17 | print((brs[b]['interfaces'][i]['underlay']['remote']).split(':')[0])") 18 | if [ -z "$ip_dsts" ]; then 19 | error_exit "No interface addresses in $topologyFile." 20 | fi 21 | 22 | # test icmp ping on each interface 23 | for ip_dst in $ip_dsts 24 | do 25 | cmd="ping -c 1 -w 5 $ip_dst" 26 | echo "Running: $cmd" 27 | recv=$($cmd | grep -E -o '[0-9]+ received' | cut -f1 -d' ') 28 | if [ "$recv" != "1" ]; then 29 | error_exit "ICMP ping failed from $ip_dst." 30 | else 31 | echo "ICMP ping succeeded." 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /webapp/web/tests/health/scmpcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # test will fail for non-zero exit and/or bytes in stderr 3 | 4 | # error exit function 5 | error_exit() 6 | { 7 | echo "$1" 1>&2 8 | exit 1 9 | } 10 | 11 | sdaddress=$(echo $2) 12 | echo "sciond address: $sdaddress" 13 | 14 | # get local IP 15 | ip=$(hostname -I | cut -d" " -f1) 16 | echo "IP found: $ip" 17 | 18 | topologyFile=$3 19 | # get remote addresses from interfaces, return paired list 20 | dsts=($(cat $topologyFile | python3 -c "import sys, json 21 | brs = json.load(sys.stdin)['border_routers'] 22 | for b in brs: 23 | for i in brs[b]['interfaces']: 24 | print(brs[b]['interfaces'][i]['isd_as']) 25 | print((brs[b]['interfaces'][i]['underlay']['remote']).split(':')[0])")) 26 | if [ -z "$dsts" ]; then 27 | error_exit "No interface addresses in $topologyFile." 28 | fi 29 | 30 | # test scmp echo on each interface 31 | for ((i=0; i<${#dsts[@]}; i+=2)) 32 | do 33 | # if no response under default scmp ping timeout consider connection failed 34 | ia_dst="${dsts[i]}" 35 | ip_dst="${dsts[i+1]}" 36 | cmd="scion ping -c 1 --sciond $sdaddress --timeout 5s $ia_dst,[$ip_dst]" 37 | echo "Running: $cmd" 38 | recv=$($cmd | grep -E -o '[0-9]+ received' | cut -f1 -d' ') 39 | if [ "$recv" != "1" ]; then 40 | error_exit "SCMP echo failed from $ia_dst,[$ip_dst]." 41 | else 42 | echo "SCMP echo succeeded." 43 | fi 44 | done 45 | -------------------------------------------------------------------------------- /webapp/web/tests/health/testAvailDiskSpace.sh: -------------------------------------------------------------------------------- 1 | # test if the available disk space is greater than 2GB, fail if not 2 | 3 | # An error exit function 4 | error_exit() 5 | { 6 | echo "$1" 1>&2 7 | exit 1 8 | } 9 | 10 | # NOTE: Since 'df' reports memory in base-2, we will measure in 11 | # base-10 to ensure we have at least 98% of the memory we require and still 12 | # maintain readability in this script. 13 | 14 | # get the available space for this virtual machine 15 | availSpace=$(df | grep '/' -w | tr -s ' ' | cut -d ' ' -f4) 16 | echo "Size of available space: $((availSpace / 1000000))GB." 17 | 18 | # test if the available disk space is greater than 2GB 19 | if [ "$availSpace" -lt 2000000 ]; then 20 | error_exit "Error: Available disk space less than 2GB, please destroy your virtual machine and create a new one" 21 | else 22 | echo "Test for available disk space succeeds." 23 | exit 0 24 | fi 25 | -------------------------------------------------------------------------------- /webapp/web/tests/health/testSCIONRunning.sh: -------------------------------------------------------------------------------- 1 | # check if SCION is running 2 | 3 | # error exit function 4 | error_exit() 5 | { 6 | echo "$1" 1>&2 7 | exit 1 8 | } 9 | 10 | # allow IA via args 11 | iaFile=$(echo $1 | sed "s/:/_/g") 12 | echo "IA found: $iaFile" 13 | isd=$(echo ${iaFile} | cut -d"-" -f1) 14 | 15 | sdaddress=$(echo $2) 16 | echo "sciond address: $sdaddress" 17 | 18 | # check if "./scion.sh status" returns anything, fail if it does 19 | if [ $isd -ge 16 ]; then 20 | status="$(systemctl -t service --failed | grep scion*.service 2>&1)" 21 | else 22 | # localhost testing 23 | [ -z "$SCION_ROOT" ] && error_exit "SCION_ROOT env variable not set, scion.sh can't be found" 24 | cd $SCION_ROOT 25 | status="$($SCION_ROOT/scion.sh status 2>&1)" 26 | fi 27 | 28 | if [[ $status ]] 29 | then 30 | echo "SCION status has reported a problem: $status." 31 | error_exit "Stop and start SCION again as following then retry the test: 32 | 33 | $ cd \$SC 34 | $ ./scion.sh stop 35 | $ ./scion.sh start 36 | $ ./scion.sh status 37 | 38 | if the test still fails, please contact us and copy the following msg: 39 | $status" 40 | else 41 | echo "SCION running status is normal." 42 | fi 43 | 44 | # check TCP sciond socket is running; split host:port for netcat 45 | cmd="nc -z $(echo "$sdaddress" | sed -e 's/\[\?\([^][]*\)\]\?:/\1 /')" 46 | echo "Running: $cmd" 47 | if $cmd 2>&1; then 48 | echo "SCIOND is listening on $sdaddress." 49 | else 50 | error_exit "SCIOND did not respond on $sdaddress." 51 | fi 52 | 53 | echo "Test for SCION running succeeds." 54 | -------------------------------------------------------------------------------- /webapp/web/tests/health/testTotalMem.sh: -------------------------------------------------------------------------------- 1 | # test if the total memory is greater than 2GB, fail if not 2 | 3 | # An error exit function 4 | error_exit() 5 | { 6 | echo "$1" 1>&2 7 | exit 1 8 | } 9 | 10 | # NOTE: Since 'free' reports memory in base-2, and may have some kb 11 | # trimmed off the total for video and other allocations, we will measure in 12 | # base-10 to ensure we have at least 98% of the memory we require and still 13 | # maintain readability in this script. We allocated 2048000kb for each VM, 14 | # however the total memory can be altered as virtualbox demands. 15 | 16 | # get the total memory for this virtual machine 17 | totalMem=$(free | grep 'Mem:' | tr -s ' ' | cut -d ' ' -f2) 18 | echo "Size of total memory: $((totalMem / 1000000))GB." 19 | 20 | # test if the total memory is greater than 2GB 21 | if [ "$totalMem" -lt 2000000 ]; then 22 | error_exit "Error: Total memory less than 2GB, please contact us." 23 | else 24 | echo "Test for total memory succeeds." 25 | exit 0 26 | fi 27 | -------------------------------------------------------------------------------- /webapp/web/tests/health/testVPN.sh: -------------------------------------------------------------------------------- 1 | # test if the VPN configuration is correct, fail if not 2 | 3 | set -e 4 | # error exit function 5 | error_exit() 6 | { 7 | echo "$1" 1>&2 8 | exit 1 9 | } 10 | 11 | # test if this AS uses VPN, exit directly if not. 12 | if [ ! -f /etc/openvpn/client.conf ] 13 | then 14 | echo "VPN is not configured for this AS." 15 | exit 0 16 | fi 17 | 18 | # lines describing the tun0 interface 19 | targetLines=$(ip address show dev tun0) 20 | 21 | ### 1.check if tun0 interface is present 22 | if [ $(echo "$targetLines" | wc -l) -eq 1 ] 23 | then 24 | error_exit "You are probably behind a firewall that does not allow UDP traffic on port 1194. Please check your /var/log/syslog to find out if there had been a timeout while trying to establish the openvpn connection (search for ovpn-client in the /var/log/syslog file). If you find out that the tun0 interface was not brought up because timeouts between your client and the VPN server, it is an indication that a firewall is filtering the traffic: please contact your IT service to add an exception for your machine and port 1194." 25 | fi 26 | 27 | # ip address of the tun0 interface 28 | ipAddress=$(echo "$targetLines" | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -n1) 29 | topologyFile=$3 30 | # ip address specified in the topology file. If "bind" parameter present, use that one; "public" if not 31 | ipTopology=$(cat $topologyFile | python3 -c "import sys, json 32 | brs = json.load(sys.stdin)['BorderRouters'] 33 | interfaces=next(iter(brs.values()))['Interfaces'] 34 | inter=next(iter(interfaces.values())) 35 | print(inter['BindOverlay'] if 'BindOverlay' in inter.keys() else inter['PublicOverlay']['Addr'])") 36 | if [ -z "$ipTopology" ]; then 37 | error_exit "No interface addresses in $topologyFile." 38 | fi 39 | 40 | # 2.check if the ip address from the tun0 interface is consistent with the one from the topology. 41 | if [[ $ipAddress != $ipTopology ]]; then 42 | error_exit "The tun0 IP address doesn't match the IP address in your topology file, please destroy the existing virtual machine and remove its settings by first logging out of it and then running the steps described in the snippet vagrant destroy. After destroying the virtual machine, we can delete its configuration: 43 | 44 | $ vagrant destroy -f 45 | . 46 | . 47 | . 48 | $ cd .. 49 | $ pwd 50 | /home/user/Downloads/ 51 | $ rm -r user@example.com_17-ffaa_1_64 52 | Now check in the Coordinator webpage that your AS is correctly attached to your AP of choice, and that you are using the right tarball file. If in doubt, you can always click on Re-download my SCIONLab AS Configuration to get it again. Re-download does not configure the AS, but returns the latest configuration the Coordinator has for it. Wait 15 minutes (the reason being sometimes the attachment point needs 15 minutes to process your request). You should have received an email stating the success of your request. In the hopefully successful state, start again from the checking tarbal step. If after waiting these 15 minutes you did not receive the success email, or you received it but still don't see the same IP address in the tun0 interface as in the topology file, contact us." 53 | fi 54 | 55 | echo "VPN tun0 interface found at $ipAddress matching binding or public address" 56 | echo "Test for VPN succeeds." 57 | -------------------------------------------------------------------------------- /webapp/web/tests/health/timecheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # test will fail for non-zero exit and/or bytes in stderr 3 | 4 | # define threshold 5 | min_sec=10 6 | 7 | echo "Checking time using google.com now:" 8 | tl=$(date '+%s') 9 | ts=$(date '+%s' --date="$(curl -sI google.com | sed -n '/Date:\s*/s///p')") 10 | diff=$((ts-tl)) 11 | diff=${diff#-} # abs(diff) 12 | echo Time diff: "$diff"s 13 | if [ $diff -gt $min_sec ]; then 14 | echo "Offset must be within $min_sec seconds." 15 | exit 1 16 | fi 17 | --------------------------------------------------------------------------------