├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── codeql-analysis.yml │ ├── docker.yml │ └── pinecone.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.sim ├── LICENSE ├── NOTICE ├── README.md ├── build └── gobind │ ├── build.sh │ ├── gobind.go │ ├── platform_darwin.go │ └── platform_other.go ├── cmd ├── pinecone │ └── main.go ├── pineconeip │ ├── main.go │ └── tun │ │ ├── tun.go │ │ ├── tun_darwin.go │ │ └── tun_linux.go └── pineconesim │ ├── README.md │ ├── ext │ └── purify.min.js │ ├── graphs │ ├── dn42.txt │ ├── empty.txt │ ├── fc00.txt │ ├── freifunk-bielefield.txt │ ├── graph-250-2.txt │ ├── graph-250.txt │ ├── grid4-0049.txt │ ├── grid4-2025.txt │ ├── line-0050.txt │ ├── meshnetlab-graphconv │ │ └── main.go │ ├── rtree-0050.txt │ ├── sim-large.txt │ ├── sim-medium.txt │ ├── sim-skitter.txt │ ├── sim.txt │ └── yggdrasil.txt │ ├── main.go │ ├── page.html │ ├── sequences │ ├── api_reference.json │ ├── debug.json │ ├── example_alice_and_friends.json │ └── example_drop_snek_proto.json │ ├── simulator │ ├── adversary │ │ └── drop_packets.go │ ├── api.go │ ├── commands.go │ ├── events.go │ ├── interface.go │ ├── links.go │ ├── nodes.go │ ├── pathfind.go │ ├── ping.go │ ├── realpaths.go │ ├── router.go │ ├── simulator.go │ ├── state.go │ └── types.go │ └── ui │ ├── default.css │ ├── main.js │ ├── modules │ ├── graph.js │ ├── server-api.js │ ├── ui.js │ └── ui │ │ ├── common.js │ │ ├── nodes-form.js │ │ └── peerings-form.js │ └── websocket-worker.js ├── connections └── manager.go ├── docs ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _sass │ └── custom │ │ └── custom.scss ├── index.md ├── introduction.md ├── introduction │ ├── 1_network_formation.md │ ├── 2_node_anatomy.md │ └── 3_frame_forwarding.md ├── peer_management.md ├── peer_management │ ├── 1_peer_connects.md │ └── 2_peer_disconnects.md ├── spanning_tree.md ├── spanning_tree │ ├── 1_root_node.md │ ├── 2_root_announcements.md │ ├── 3_sending_root_announcements.md │ ├── 4_handling_root_announcements.md │ ├── 5_parent_selection.md │ ├── 6_coordinates.md │ ├── 7_root_election.md │ └── 8_next_hop.md ├── virtual_snake.md └── virtual_snake │ ├── 1_neighbours.md │ ├── 2_bootstrapping.md │ ├── 3_bootstraps.md │ ├── 4_next_hop.md │ └── 5_maintenance.md ├── go.mod ├── go.sum ├── multicast ├── multicast.go ├── platform_darwin.go ├── platform_other.go ├── platform_unix.go └── platform_windows.go ├── pinecone.lua ├── router ├── api.go ├── consts.go ├── events │ └── events.go ├── manhole.go ├── options.go ├── packetconn.go ├── peer.go ├── pools.go ├── pprof.go ├── queue.go ├── queuefairfifo.go ├── queuefifo.go ├── queuefifo_test.go ├── queuelifo.go ├── router.go ├── state.go ├── state_broadcast.go ├── state_forward.go ├── state_snek.go ├── state_snek_test.go ├── state_tree.go ├── state_tree_test.go └── version.go ├── sessions ├── dial.go ├── http.go ├── listen.go ├── sessions.go └── streams.go ├── types ├── announcement.go ├── announcement_test.go ├── broadcast.go ├── coordinates.go ├── coordinates_test.go ├── ed25519.go ├── ed25519_test.go ├── frame.go ├── frame_test.go ├── logger.go ├── signaturehop.go ├── varu64.go ├── varu64_test.go ├── virtualsnake.go └── virtualsnake_test.go └── util ├── distance.go ├── distance_test.go ├── overlay.go ├── overlay_test.go ├── slowconn.go └── websocket.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: matrixdotorg 2 | liberapay: matrixdotorg 3 | custom: https://paypal.me/matrixdotorg 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | Signed-off-by: `Your Name ` 3 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '39 21 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go', 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/docker/build-push-action 2 | 3 | name: "Docker" 4 | 5 | on: 6 | workflow_dispatch: # A build was manually requested 7 | workflow_call: # Another pipeline called us 8 | 9 | env: 10 | GHCR_REPOSITORY: matrix-org/pinecone 11 | PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7 12 | 13 | jobs: 14 | build: 15 | name: Image 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | - name: Get release tag 24 | if: github.event_name == 'release' # Only for GitHub releases 25 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v1 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v1 30 | - name: Login to GitHub Containers 31 | uses: docker/login-action@v1 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.repository_owner }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Build image 38 | if: github.ref_name == 'main' 39 | id: docker_build 40 | uses: docker/build-push-action@v2 41 | with: 42 | cache-from: type=gha 43 | cache-to: type=gha,mode=max 44 | context: . 45 | file: ./Dockerfile 46 | platforms: ${{ env.PLATFORMS }} 47 | push: true 48 | tags: | 49 | ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ github.ref_name }} 50 | -------------------------------------------------------------------------------- /.github/workflows/pinecone.yml: -------------------------------------------------------------------------------- 1 | name: Pinecone 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: "stable" 20 | 21 | - name: Build Pinecone 22 | run: go build -v ./... 23 | 24 | - name: Run unit tests 25 | run: go test -v ./... 26 | 27 | docker: 28 | name: Docker 29 | permissions: 30 | packages: write 31 | contents: read 32 | if: github.repository == 'matrix-org/pinecone' && github.ref_name == 'main' 33 | needs: [build] 34 | uses: matrix-org/pinecone/.github/workflows/docker.yml@main 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /pinecone 3 | .exe 4 | 5 | # Stuff from GitHub Pages 6 | docs/_site 7 | .jekyll-metadata 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/golang:alpine AS base 2 | 3 | RUN apk --update --no-cache add bash build-base git 4 | 5 | WORKDIR /build 6 | 7 | COPY . /build 8 | 9 | RUN mkdir -p bin 10 | RUN go build -trimpath -o bin/ ./cmd/pinecone 11 | 12 | FROM alpine:latest 13 | LABEL org.opencontainers.image.title="Pinecone" 14 | LABEL org.opencontainers.image.description="Standalone Pinecone router" 15 | LABEL org.opencontainers.image.source="https://github.com/matrix-org/pinecone" 16 | LABEL org.opencontainers.image.licenses="Apache-2.0" 17 | 18 | COPY --from=base /build/bin/* /usr/bin/ 19 | 20 | EXPOSE 65432/tcp 21 | EXPOSE 65433/tcp 22 | 23 | ENTRYPOINT ["/usr/bin/pinecone", "-listenws=:65433", "-listen=:65432", "-manhole"] -------------------------------------------------------------------------------- /Dockerfile.sim: -------------------------------------------------------------------------------- 1 | FROM docker.io/golang:alpine AS base 2 | 3 | RUN apk --update --no-cache add bash build-base git 4 | 5 | WORKDIR /build 6 | 7 | COPY . /build 8 | 9 | RUN mkdir -p bin 10 | RUN go build -trimpath -o bin/ ./cmd/pineconesim 11 | 12 | FROM alpine:latest 13 | LABEL org.opencontainers.image.title="Pinecone Simulator" 14 | LABEL org.opencontainers.image.description="All-in-one Pinecone Simulator" 15 | LABEL org.opencontainers.image.source="https://github.com/matrix-org/pinecone" 16 | LABEL org.opencontainers.image.licenses="Apache-2.0" 17 | 18 | RUN mkdir -p /cmd/pineconesim/graphs/ 19 | 20 | COPY --from=base /build/bin/* /usr/bin/ 21 | COPY --from=base /build/cmd/pineconesim/ui /cmd/pineconesim/ui 22 | COPY --from=base /build/cmd/pineconesim/ext /cmd/pineconesim/ext 23 | COPY --from=base /build/cmd/pineconesim/page.html /cmd/pineconesim/ 24 | COPY --from=base /build/cmd/pineconesim/graphs/*.txt /cmd/pineconesim/graphs/ 25 | 26 | EXPOSE 65432/tcp 27 | 28 | ENTRYPOINT ["/usr/bin/pineconesim"] 29 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Pinecone 2 | Peer-to-peer overlay routing for the Matrix ecosystem 3 | Copyright 2021 The Matrix.org Foundation C.I.C. -------------------------------------------------------------------------------- /build/gobind/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TARGET="" 4 | 5 | while getopts "aim" option 6 | do 7 | case "$option" 8 | in 9 | a) gomobile bind -v -target android -trimpath -ldflags="-s -w" github.com/matrix-org/pinecone/build/gobind ;; 10 | i) gomobile bind -v -target ios -trimpath -ldflags="" github.com/matrix-org/pinecone/build/gobind ;; 11 | m) gomobile bind -v -target macos -trimpath -ldflags="" github.com/matrix-org/pinecone/build/gobind ;; 12 | *) echo "No target specified, specify -a or -i"; exit 1 ;; 13 | esac 14 | done -------------------------------------------------------------------------------- /build/gobind/gobind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 gobind 16 | 17 | import ( 18 | "context" 19 | "crypto/ed25519" 20 | "encoding/hex" 21 | "io" 22 | "log" 23 | "net" 24 | "sync" 25 | "time" 26 | 27 | pineconeConnections "github.com/matrix-org/pinecone/connections" 28 | pineconeMulticast "github.com/matrix-org/pinecone/multicast" 29 | pineconeRouter "github.com/matrix-org/pinecone/router" 30 | "github.com/matrix-org/pinecone/types" 31 | 32 | _ "golang.org/x/mobile/bind" 33 | ) 34 | 35 | const ( 36 | PeerTypeRemote = pineconeRouter.PeerTypeRemote 37 | PeerTypeMulticast = pineconeRouter.PeerTypeMulticast 38 | PeerTypeBluetooth = pineconeRouter.PeerTypeBluetooth 39 | PeerTypeBonjour = pineconeRouter.PeerTypeBonjour 40 | ) 41 | 42 | type Pinecone struct { 43 | ctx context.Context 44 | cancel context.CancelFunc 45 | logger *log.Logger 46 | PineconeRouter *pineconeRouter.Router 47 | PineconeMulticast *pineconeMulticast.Multicast 48 | PineconeManager *pineconeConnections.ConnectionManager 49 | } 50 | 51 | func (m *Pinecone) PeerCount(peertype int) int { 52 | return m.PineconeRouter.PeerCount(peertype) 53 | } 54 | 55 | func (m *Pinecone) PublicKey() string { 56 | return m.PineconeRouter.PublicKey().String() 57 | } 58 | 59 | func (m *Pinecone) SetMulticastEnabled(enabled bool) { 60 | if enabled { 61 | m.PineconeMulticast.Start() 62 | } else { 63 | m.PineconeMulticast.Stop() 64 | m.DisconnectType(int(pineconeRouter.PeerTypeMulticast)) 65 | } 66 | } 67 | 68 | func (m *Pinecone) SetStaticPeer(uri string) { 69 | m.PineconeManager.RemovePeers() 70 | if uri != "" { 71 | m.PineconeManager.AddPeer(uri) 72 | } 73 | } 74 | 75 | func (m *Pinecone) DisconnectType(peertype int) { 76 | for _, p := range m.PineconeRouter.Peers() { 77 | if int(peertype) == p.PeerType { 78 | m.PineconeRouter.Disconnect(types.SwitchPortID(p.Port), nil) 79 | } 80 | } 81 | } 82 | 83 | func (m *Pinecone) DisconnectZone(zone string) { 84 | for _, p := range m.PineconeRouter.Peers() { 85 | if zone == p.Zone { 86 | m.PineconeRouter.Disconnect(types.SwitchPortID(p.Port), nil) 87 | } 88 | } 89 | } 90 | 91 | func (m *Pinecone) DisconnectPort(port int) { 92 | m.PineconeRouter.Disconnect(types.SwitchPortID(port), nil) 93 | } 94 | 95 | func (m *Pinecone) Conduit(zone string, peertype int) (*Conduit, error) { 96 | l, r := net.Pipe() 97 | conduit := &Conduit{conn: r, port: 0} 98 | go func() { 99 | conduit.portMutex.Lock() 100 | defer conduit.portMutex.Unlock() 101 | loop: 102 | for i := 1; i <= 10; i++ { 103 | var err error 104 | conduit.port, err = m.PineconeRouter.Connect( 105 | l, 106 | pineconeRouter.ConnectionZone(zone), 107 | pineconeRouter.ConnectionPeerType(peertype), 108 | ) 109 | switch err { 110 | case io.ErrClosedPipe: 111 | return 112 | case io.EOF: 113 | break loop 114 | case nil: 115 | return 116 | default: 117 | time.Sleep(time.Second) 118 | } 119 | } 120 | _ = l.Close() 121 | _ = r.Close() 122 | }() 123 | return conduit, nil 124 | } 125 | 126 | // nolint:gocyclo 127 | func (m *Pinecone) Start() { 128 | pk, sk, err := ed25519.GenerateKey(nil) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | m.ctx, m.cancel = context.WithCancel(context.Background()) 134 | 135 | m.logger = log.New(BindLogger{}, "Pinecone: ", 0) 136 | m.logger.Println("Public key:", hex.EncodeToString(pk)) 137 | 138 | m.PineconeRouter = pineconeRouter.NewRouter(m.logger, sk) 139 | m.PineconeMulticast = pineconeMulticast.NewMulticast(m.logger, m.PineconeRouter) 140 | m.PineconeManager = pineconeConnections.NewConnectionManager(m.PineconeRouter, nil) 141 | } 142 | 143 | func (m *Pinecone) Stop() { 144 | m.PineconeMulticast.Stop() 145 | _ = m.PineconeRouter.Close() 146 | m.cancel() 147 | } 148 | 149 | const MaxFrameSize = types.MaxFrameSize 150 | 151 | type Conduit struct { 152 | conn net.Conn 153 | port types.SwitchPortID 154 | portMutex sync.Mutex 155 | } 156 | 157 | func (c *Conduit) Port() int { 158 | c.portMutex.Lock() 159 | defer c.portMutex.Unlock() 160 | return int(c.port) 161 | } 162 | 163 | func (c *Conduit) Read(b []byte) (int, error) { 164 | return c.conn.Read(b) 165 | } 166 | 167 | func (c *Conduit) ReadCopy() ([]byte, error) { 168 | var buf [65535 * 2]byte 169 | n, err := c.conn.Read(buf[:]) 170 | if err != nil { 171 | return nil, err 172 | } 173 | return buf[:n], nil 174 | } 175 | 176 | func (c *Conduit) Write(b []byte) (int, error) { 177 | return c.conn.Write(b) 178 | } 179 | 180 | func (c *Conduit) Close() error { 181 | return c.conn.Close() 182 | } 183 | -------------------------------------------------------------------------------- /build/gobind/platform_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 ios || darwin 16 | // +build ios darwin 17 | 18 | package gobind 19 | 20 | /* 21 | #cgo CFLAGS: -x objective-c 22 | #cgo LDFLAGS: -framework Foundation 23 | #import 24 | void Log(const char *text) { 25 | NSString *nss = [NSString stringWithUTF8String:text]; 26 | NSLog(@"%@", nss); 27 | } 28 | */ 29 | import "C" 30 | import "unsafe" 31 | 32 | type BindLogger struct { 33 | } 34 | 35 | func (nsl BindLogger) Write(p []byte) (n int, err error) { 36 | p = append(p, 0) 37 | cstr := (*C.char)(unsafe.Pointer(&p[0])) 38 | C.Log(cstr) 39 | return len(p), nil 40 | } 41 | -------------------------------------------------------------------------------- /build/gobind/platform_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 !ios && !darwin 16 | // +build !ios,!darwin 17 | 18 | package gobind 19 | 20 | import "log" 21 | 22 | type BindLogger struct{} 23 | 24 | func (nsl BindLogger) Write(p []byte) (n int, err error) { 25 | log.Println(string(p)) 26 | return len(p), nil 27 | } 28 | -------------------------------------------------------------------------------- /cmd/pinecone/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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/ed25519" 20 | "encoding/hex" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "net" 25 | "os" 26 | "os/signal" 27 | "strings" 28 | "syscall" 29 | 30 | "net/http" 31 | 32 | "github.com/gorilla/websocket" 33 | "github.com/matrix-org/pinecone/connections" 34 | "github.com/matrix-org/pinecone/multicast" 35 | "github.com/matrix-org/pinecone/router" 36 | "github.com/matrix-org/pinecone/util" 37 | ) 38 | 39 | func main() { 40 | sigs := make(chan os.Signal, 1) 41 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 42 | 43 | listentcp := flag.String("listen", ":0", "address to listen for TCP connections") 44 | listenws := flag.String("listenws", ":0", "address to listen for WebSockets connections") 45 | connect := flag.String("connect", "", "peers to connect to") 46 | secretkey := flag.String("secretkey", "", "hexadecimal encoded ed25519 key") 47 | manhole := flag.Bool("manhole", false, "enable the manhole (requires WebSocket listener to be active)") 48 | flag.Parse() 49 | 50 | var sk ed25519.PrivateKey 51 | if len(*secretkey) != 0 { 52 | secretkeyHex, err := hex.DecodeString(*secretkey) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | sk = ed25519.NewKeyFromSeed(secretkeyHex) 58 | } else { 59 | var err error 60 | _, sk, err = ed25519.GenerateKey(nil) 61 | if err != nil { 62 | panic(err) 63 | } 64 | } 65 | 66 | logger := log.New(os.Stdout, "", 0) 67 | if hostPort := os.Getenv("PPROFLISTEN"); hostPort != "" { 68 | logger.Println("Starting pprof on", hostPort) 69 | go func() { 70 | _ = http.ListenAndServe(hostPort, nil) 71 | }() 72 | } 73 | 74 | listener := net.ListenConfig{} 75 | 76 | pineconeRouter := router.NewRouter(logger, sk, router.RouterOptionBlackhole(true)) 77 | pineconeMulticast := multicast.NewMulticast(logger, pineconeRouter) 78 | pineconeMulticast.Start() 79 | pineconeManager := connections.NewConnectionManager(pineconeRouter, nil) 80 | 81 | if connect != nil && *connect != "" { 82 | for _, uri := range strings.Split(*connect, ",") { 83 | pineconeManager.AddPeer(strings.TrimSpace(uri)) 84 | } 85 | } 86 | 87 | if listenws != nil && *listenws != "" { 88 | go func() { 89 | var upgrader = websocket.Upgrader{} 90 | http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 91 | conn, err := upgrader.Upgrade(w, r, nil) 92 | if err != nil { 93 | log.Println(err) 94 | return 95 | } 96 | 97 | if _, err := pineconeRouter.Connect( 98 | util.WrapWebSocketConn(conn), 99 | router.ConnectionURI(conn.RemoteAddr().String()), 100 | router.ConnectionPeerType(router.PeerTypeRemote), 101 | router.ConnectionZone("websocket"), 102 | ); err != nil { 103 | fmt.Println("Inbound WS connection", conn.RemoteAddr(), "error:", err) 104 | _ = conn.Close() 105 | } else { 106 | fmt.Println("Inbound WS connection", conn.RemoteAddr(), "is connected") 107 | } 108 | }) 109 | 110 | if *manhole { 111 | fmt.Println("Enabling manhole on HTTP listener") 112 | http.DefaultServeMux.HandleFunc("/manhole", func(w http.ResponseWriter, r *http.Request) { 113 | pineconeRouter.ManholeHandler(w, r) 114 | }) 115 | } 116 | 117 | listener, err := listener.Listen(context.Background(), "tcp", *listenws) 118 | if err != nil { 119 | panic(err) 120 | } 121 | 122 | fmt.Printf("Listening for WebSockets on http://%s\n", listener.Addr()) 123 | 124 | if err := http.Serve(listener, http.DefaultServeMux); err != nil { 125 | panic(err) 126 | } 127 | }() 128 | } 129 | 130 | if listentcp != nil && *listentcp != "" { 131 | go func() { 132 | listener, err := listener.Listen(context.Background(), "tcp", *listentcp) 133 | if err != nil { 134 | panic(err) 135 | } 136 | 137 | fmt.Println("Listening on", listener.Addr()) 138 | 139 | for { 140 | conn, err := listener.Accept() 141 | if err != nil { 142 | panic(err) 143 | } 144 | 145 | if _, err := pineconeRouter.Connect( 146 | conn, 147 | router.ConnectionURI(conn.RemoteAddr().String()), 148 | router.ConnectionPeerType(router.PeerTypeRemote), 149 | ); err != nil { 150 | fmt.Println("Inbound TCP connection", conn.RemoteAddr(), "error:", err) 151 | _ = conn.Close() 152 | } else { 153 | fmt.Println("Inbound TCP connection", conn.RemoteAddr(), "is connected") 154 | } 155 | } 156 | }() 157 | } 158 | 159 | <-sigs 160 | } 161 | -------------------------------------------------------------------------------- /cmd/pineconeip/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 | "crypto/ed25519" 19 | "flag" 20 | "fmt" 21 | "log" 22 | "net" 23 | "os" 24 | "os/signal" 25 | "runtime/pprof" 26 | "syscall" 27 | "time" 28 | 29 | "net/http" 30 | 31 | "github.com/matrix-org/pinecone/cmd/pineconeip/tun" 32 | "github.com/matrix-org/pinecone/connections" 33 | "github.com/matrix-org/pinecone/multicast" 34 | "github.com/matrix-org/pinecone/router" 35 | ) 36 | 37 | func main() { 38 | _, sk, err := ed25519.GenerateKey(nil) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | listen := flag.String("listen", "", "address to listen on") 44 | connect := flag.String("connect", "", "peer to connect to") 45 | flag.Parse() 46 | 47 | addr, err := net.ResolveTCPAddr("tcp", *listen) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | listener, err := net.ListenTCP("tcp", addr) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | logger := log.New(os.Stdout, "", 0) 58 | if hostPort := os.Getenv("PPROFLISTEN"); hostPort != "" { 59 | go func() { 60 | listener, err := net.Listen("tcp", hostPort) 61 | if err != nil { 62 | panic(err) 63 | } 64 | logger.Println("Starting pprof on", listener.Addr()) 65 | if err := http.Serve(listener, nil); err != nil { 66 | panic(err) 67 | } 68 | }() 69 | } 70 | 71 | pineconeRouter := router.NewRouter(logger, sk) 72 | pineconeMulticast := multicast.NewMulticast(logger, pineconeRouter) 73 | pineconeMulticast.Start() 74 | pineconeManager := connections.NewConnectionManager(pineconeRouter, nil) 75 | pineconeTUN, err := tun.NewTUN(pineconeRouter) 76 | if err != nil { 77 | panic(err) 78 | } 79 | _ = pineconeTUN 80 | 81 | if connect != nil && *connect != "" { 82 | pineconeManager.AddPeer(*connect) 83 | } 84 | 85 | go func() { 86 | fmt.Println("Listening on", listener.Addr()) 87 | 88 | for { 89 | conn, err := listener.AcceptTCP() 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | port, err := pineconeRouter.Connect( 95 | conn, 96 | router.ConnectionURI(conn.RemoteAddr().String()), 97 | router.ConnectionPeerType(router.PeerTypeRemote), 98 | ) 99 | if err != nil { 100 | panic(err) 101 | } 102 | 103 | fmt.Println("Inbound connection", conn.RemoteAddr(), "is connected to port", port) 104 | } 105 | }() 106 | 107 | sigs := make(chan os.Signal, 1) 108 | signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGUSR2) 109 | for { 110 | switch <-sigs { 111 | case syscall.SIGUSR1: 112 | fn := fmt.Sprintf("/tmp/profile.%d", os.Getpid()) 113 | logger.Println("Requested profile:", fn) 114 | fp, err := os.Create(fn) 115 | if err != nil { 116 | logger.Println("Failed to create profile:", err) 117 | return 118 | } 119 | defer fp.Close() 120 | if err := pprof.StartCPUProfile(fp); err != nil { 121 | logger.Println("Failed to start profiling:", err) 122 | return 123 | } 124 | time.AfterFunc(time.Second*10, func() { 125 | pprof.StopCPUProfile() 126 | logger.Println("Profile written:", fn) 127 | }) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /cmd/pineconeip/tun/tun.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/matrix-org/pinecone/router" 8 | "github.com/matrix-org/pinecone/types" 9 | 10 | wgtun "golang.zx2c4.com/wireguard/tun" 11 | ) 12 | 13 | const TUN_OFFSET_BYTES = 4 14 | 15 | type TUN struct { 16 | r *router.Router 17 | iface wgtun.Device 18 | // partialToFull map[types.PublicKey]types.PublicKey 19 | // mutex sync.RWMutex 20 | } 21 | 22 | /* 23 | func (t *TUN) Lookup(partial types.PublicKey) types.PublicKey { 24 | t.mutex.RLock() 25 | pk, ok := t.partialToFull[partial] 26 | t.mutex.RUnlock() 27 | if ok { 28 | return pk 29 | } 30 | full, addr, err := t.r.DHTSearch(context.Background(), partial[:], false) 31 | if err != nil { 32 | fmt.Println("DHT search failed:", err) 33 | return types.PublicKey{} 34 | } 35 | fmt.Println("DHT search from", addr, "returned", full) 36 | t.mutex.Lock() 37 | t.partialToFull[partial] = full 38 | t.mutex.Unlock() 39 | return full 40 | } 41 | */ 42 | 43 | func AddressForPublicKey(pk types.PublicKey) net.IP { 44 | a := [16]byte{0xFD} 45 | copy(a[1:], pk[:]) 46 | return a[:] 47 | } 48 | 49 | func PublicKeyForAddress(a net.IP) types.PublicKey { 50 | if a[0] != 0xFD { 51 | return types.PublicKey{} 52 | } 53 | pk := types.PublicKey{} 54 | copy(pk[:], a[1:]) 55 | return pk 56 | } 57 | 58 | func NewTUN(r *router.Router) (*TUN, error) { 59 | t := &TUN{ 60 | r: r, 61 | // partialToFull: make(map[types.PublicKey]types.PublicKey), 62 | } 63 | addr := AddressForPublicKey(r.PublicKey()) 64 | if err := t.setup(addr); err != nil { 65 | return nil, fmt.Errorf("t.setup: %w", err) 66 | } 67 | go t.read() 68 | go t.write() 69 | return t, nil 70 | } 71 | 72 | func (t *TUN) read() { 73 | var buf [TUN_OFFSET_BYTES + 65536]byte 74 | for { 75 | n, err := t.iface.Read(buf[:], TUN_OFFSET_BYTES) 76 | if n <= TUN_OFFSET_BYTES || err != nil { 77 | fmt.Println("Error reading TUN:", err) 78 | _ = t.iface.Flush() 79 | continue 80 | } 81 | bs := buf[TUN_OFFSET_BYTES : TUN_OFFSET_BYTES+n] 82 | if bs[0]&0xf0 != 0x60 { 83 | continue 84 | } 85 | dst := net.IP(bs[24:40]) 86 | pk := PublicKeyForAddress(dst) 87 | if dst[0] != 0xFD { 88 | continue 89 | } 90 | ns, err := t.r.WriteTo(bs, pk) 91 | if err != nil { 92 | fmt.Println("t.r.WriteTo:", err) 93 | continue 94 | } 95 | if ns < n { 96 | fmt.Println("Wrote", ns, "bytes but should have wrote", n, "bytes") 97 | continue 98 | } 99 | } 100 | } 101 | 102 | func (t *TUN) write() { 103 | var buf [TUN_OFFSET_BYTES + 65536]byte 104 | for { 105 | n, _, err := t.r.ReadFrom(buf[TUN_OFFSET_BYTES:]) 106 | if err != nil { 107 | fmt.Println("Error reading Pinecone:", err) 108 | continue 109 | } 110 | _, err = t.iface.Write(buf[:TUN_OFFSET_BYTES+n], TUN_OFFSET_BYTES) 111 | if err != nil { 112 | fmt.Println("Error writing TUN:", err) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /cmd/pineconeip/tun/tun_darwin.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/unix" 12 | 13 | wgtun "golang.zx2c4.com/wireguard/tun" 14 | ) 15 | 16 | // Configures the "utun" adapter with the correct IPv6 address and MTU. 17 | func (tun *TUN) setup(addr net.IP) error { 18 | iface, err := wgtun.CreateTUN("utun", 65000) 19 | if err != nil { 20 | panic(err) 21 | } 22 | tun.iface = iface 23 | return tun.setupAddress(addr.String()) 24 | } 25 | 26 | const ( 27 | darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h 28 | darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h 29 | darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h 30 | darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h 31 | ) 32 | 33 | // nolint:structcheck 34 | type in6_addrlifetime struct { 35 | ia6t_expire float64 // nolint:unused 36 | ia6t_preferred float64 // nolint:unused 37 | ia6t_vltime uint32 38 | ia6t_pltime uint32 39 | } 40 | 41 | // nolint:structcheck 42 | type sockaddr_in6 struct { 43 | sin6_len uint8 44 | sin6_family uint8 45 | sin6_port uint8 // nolint:unused 46 | sin6_flowinfo uint32 // nolint:unused 47 | sin6_addr [8]uint16 48 | sin6_scope_id uint32 // nolint:unused 49 | } 50 | 51 | // nolint:structcheck 52 | type in6_aliasreq struct { 53 | ifra_name [16]byte 54 | ifra_addr sockaddr_in6 55 | ifra_dstaddr sockaddr_in6 // nolint:unused 56 | ifra_prefixmask sockaddr_in6 57 | ifra_flags uint32 58 | ifra_lifetime in6_addrlifetime 59 | } 60 | 61 | // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using 62 | // a system socket and making direct syscalls to the kernel. 63 | func (tun *TUN) setupAddress(addr string) error { 64 | var fd int 65 | var err error 66 | 67 | if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { 68 | return fmt.Errorf("unix.Socket: %w", err) 69 | } 70 | 71 | ifname, err := tun.iface.Name() 72 | if err != nil { 73 | return fmt.Errorf("tun.iface.Name: %w", err) 74 | } 75 | 76 | var ar in6_aliasreq 77 | copy(ar.ifra_name[:], []byte(ifname)) 78 | 79 | ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask)) 80 | b := make([]byte, 16) 81 | binary.LittleEndian.PutUint16(b, uint16(0xFE00)) 82 | ar.ifra_prefixmask.sin6_addr[0] = uint16(binary.BigEndian.Uint16(b)) 83 | 84 | ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr)) 85 | ar.ifra_addr.sin6_family = unix.AF_INET6 86 | parts := strings.Split(strings.Split(addr, "/")[0], ":") 87 | for i := 0; i < 8; i++ { 88 | addr, _ := strconv.ParseUint(parts[i], 16, 16) 89 | b := make([]byte, 16) 90 | binary.LittleEndian.PutUint16(b, uint16(addr)) 91 | ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b)) 92 | } 93 | 94 | ar.ifra_flags |= darwin_IN6_IFF_NODAD 95 | ar.ifra_flags |= darwin_IN6_IFF_SECURED 96 | 97 | ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME 98 | ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME 99 | 100 | fmt.Println("Interface name:", ifname) 101 | fmt.Println("Interface address:", addr) 102 | 103 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { 104 | err = errno 105 | return fmt.Errorf("darwin_SIOCAIFADDR_IN6: %w", err) 106 | } 107 | 108 | return err 109 | } 110 | -------------------------------------------------------------------------------- /cmd/pineconeip/tun/tun_linux.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/vishvananda/netlink" 8 | wgtun "golang.zx2c4.com/wireguard/tun" 9 | ) 10 | 11 | func (tun *TUN) setup(addr net.IP) error { 12 | iface, err := wgtun.CreateTUN("\000", 65000) 13 | if err != nil { 14 | panic(err) 15 | } 16 | tun.iface = iface 17 | return tun.setupAddress(addr.String()) 18 | } 19 | 20 | func (tun *TUN) setupAddress(addr string) error { 21 | nladdr, err := netlink.ParseAddr(addr + "/8") 22 | if err != nil { 23 | return err 24 | } 25 | ifname, err := tun.iface.Name() 26 | if err != nil { 27 | return err 28 | } 29 | nlintf, err := netlink.LinkByName(ifname) 30 | if err != nil { 31 | return err 32 | } 33 | if err := netlink.AddrAdd(nlintf, nladdr); err != nil { 34 | return err 35 | } 36 | if err := netlink.LinkSetUp(nlintf); err != nil { 37 | return err 38 | } 39 | fmt.Println("Interface name:", ifname) 40 | fmt.Println("Interface address:", addr) 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/pineconesim/README.md: -------------------------------------------------------------------------------- 1 | # Pinecone Simulator 2 | 3 | The Pinecone Simulator is a tool used to test Pinecone nodes in various scenarios. These scenarios include both fixed topologies and node mobility. 4 | 5 | ## Running 6 | 7 | From the top-level Pinecone directory, run the following: 8 | ```go run cmd/pineconesim/main.go``` 9 | 10 | ## Simulator UI 11 | 12 | To access the simulator's interface, visit `localhost:65432` in your web browser. 13 | 14 | ## Development 15 | 16 | ### Design Goals 17 | 18 | The overall design goal of the simulator is to be able to handle running a large number of Pinecone nodes while still being responsive. 19 | 20 | It is preferable that the simulator can be run offline and without any external build or runtime dependencies. Having a simulator that is extremely simple to run and modify makes it easier for more developers to test Pinecone, thus making Pinecone better in return. 21 | 22 | The simulator UI runs in the web browser. This is done for a few reasons: 23 | - web browsers are ubiquitous 24 | - cross platform by default 25 | - can easily connect to a remotely running simulator 26 | - multiple developers can have a view into the same simulator instance 27 | 28 | ### Detailed Design 29 | 30 | In order to decouple the effects of running the simulator from the performance of the Pinecone node/s, the simulator works by sending/receiving state change information over channels to/from each Pinecone node. When receiving information, the relevant state is cached locally in the simulator. 31 | 32 | The simulator UI works using a websocket to the running simulator. Upon accessing the simulator instance in the browser, a websocket is established that the simulator can use to forward state change events up to the UI. The first message received from the simulator will contain the current state of the entire simulation. After that, the simulator will only send state update messages in the order that they occurred. The websocket can also be used for the UI to request changes to the running simulation by sending messages down to the running simulator. 33 | 34 | ``` 35 | +--------------+ 36 | | UI / Browser | 37 | +--------------+ 38 | ⇅ (websocket) 39 | +-------------------------------+ 40 | | Simulator | 41 | | ⤢ ⤡ (chan) | 42 | | +----------+ +----------+ | 43 | | | Pinecone | ... | Pinecone | | 44 | | | Node | | Node | | 45 | | +----------+ +----------+ | 46 | +-------------------------------+ 47 | ``` 48 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/pinecone/b35aec69f59eb9bb8bd5a8c6be30fc276d3cce96/cmd/pineconesim/graphs/empty.txt -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/grid4-0049.txt: -------------------------------------------------------------------------------- 1 | 0 1 2 | 0 7 3 | 1 2 4 | 1 8 5 | 2 3 6 | 2 9 7 | 3 4 8 | 3 10 9 | 4 5 10 | 4 11 11 | 5 6 12 | 5 12 13 | 6 13 14 | 7 8 15 | 7 14 16 | 8 9 17 | 8 15 18 | 9 10 19 | 9 16 20 | 10 11 21 | 10 17 22 | 11 12 23 | 11 18 24 | 12 13 25 | 12 19 26 | 13 20 27 | 14 15 28 | 14 21 29 | 15 16 30 | 15 22 31 | 16 17 32 | 16 23 33 | 17 18 34 | 17 24 35 | 18 19 36 | 18 25 37 | 19 20 38 | 19 26 39 | 20 27 40 | 21 22 41 | 21 28 42 | 22 23 43 | 22 29 44 | 23 24 45 | 23 30 46 | 24 25 47 | 24 31 48 | 25 26 49 | 25 32 50 | 26 27 51 | 26 33 52 | 27 34 53 | 28 29 54 | 28 35 55 | 29 30 56 | 29 36 57 | 30 31 58 | 30 37 59 | 31 32 60 | 31 38 61 | 32 33 62 | 32 39 63 | 33 34 64 | 33 40 65 | 34 41 66 | 35 36 67 | 35 42 68 | 36 37 69 | 36 43 70 | 37 38 71 | 37 44 72 | 38 39 73 | 38 45 74 | 39 40 75 | 39 46 76 | 40 41 77 | 40 47 78 | 41 48 79 | 42 43 80 | 43 44 81 | 44 45 82 | 45 46 83 | 46 47 84 | 47 48 85 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/line-0050.txt: -------------------------------------------------------------------------------- 1 | 0 1 2 | 1 2 3 | 2 3 4 | 3 4 5 | 4 5 6 | 5 6 7 | 6 7 8 | 7 8 9 | 8 9 10 | 9 10 11 | 10 11 12 | 11 12 13 | 12 13 14 | 13 14 15 | 14 15 16 | 15 16 17 | 16 17 18 | 17 18 19 | 18 19 20 | 19 20 21 | 20 21 22 | 21 22 23 | 22 23 24 | 23 24 25 | 24 25 26 | 25 26 27 | 26 27 28 | 27 28 29 | 28 29 30 | 29 30 31 | 30 31 32 | 31 32 33 | 32 33 34 | 33 34 35 | 34 35 36 | 35 36 37 | 36 37 38 | 37 38 39 | 38 39 40 | 39 40 41 | 40 41 42 | 41 42 43 | 42 43 44 | 43 44 45 | 44 45 46 | 45 46 47 | 46 47 48 | 47 48 49 | 48 49 50 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/meshnetlab-graphconv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | type Graph struct { 11 | Links []struct { 12 | Source int `json:"source"` 13 | Target int `json:"target"` 14 | } `json:"links"` 15 | } 16 | 17 | var filename = flag.String("filename", "", "the .json input file to convert") 18 | 19 | func main() { 20 | flag.Parse() 21 | if filename == nil || *filename == "" { 22 | flag.PrintDefaults() 23 | return 24 | } 25 | f, err := os.Open(*filename) 26 | if err != nil { 27 | panic(err) 28 | } 29 | d := json.NewDecoder(f) 30 | var g Graph 31 | if err := d.Decode(&g); err != nil { 32 | panic(err) 33 | } 34 | for _, link := range g.Links { 35 | fmt.Printf("%d %d\n", link.Source, link.Target) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/rtree-0050.txt: -------------------------------------------------------------------------------- 1 | 1 0 2 | 2 1 3 | 3 0 4 | 4 0 5 | 5 1 6 | 6 0 7 | 7 1 8 | 8 0 9 | 9 5 10 | 10 7 11 | 11 9 12 | 12 4 13 | 13 12 14 | 14 5 15 | 15 3 16 | 16 3 17 | 17 0 18 | 18 0 19 | 19 18 20 | 20 15 21 | 21 15 22 | 22 12 23 | 23 11 24 | 24 13 25 | 25 10 26 | 26 25 27 | 27 3 28 | 28 15 29 | 29 23 30 | 30 4 31 | 31 30 32 | 32 12 33 | 33 23 34 | 34 27 35 | 35 26 36 | 36 20 37 | 37 3 38 | 38 31 39 | 39 30 40 | 40 2 41 | 41 28 42 | 42 10 43 | 43 37 44 | 44 32 45 | 45 20 46 | 46 40 47 | 47 4 48 | 48 40 49 | 49 36 50 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/sim-medium.txt: -------------------------------------------------------------------------------- 1 | 27 83 2 | 4 48 3 | 32 93 4 | 68 77 5 | 73 97 6 | 70 96 7 | 12 17 8 | 42 87 9 | 61 79 10 | 16 44 11 | 40 57 12 | 25 100 13 | 29 82 14 | 48 56 15 | 12 87 16 | 10 98 17 | 15 59 18 | 36 41 19 | 38 92 20 | 74 89 21 | 30 69 22 | 4 21 23 | 82 83 24 | 15 86 25 | 34 97 26 | 87 96 27 | 12 19 28 | 74 90 29 | 25 98 30 | 41 48 31 | 65 75 32 | 2 85 33 | 36 80 34 | 51 88 35 | 23 91 36 | 33 64 37 | 18 97 38 | 40 86 39 | 40 58 40 | 4 64 41 | 17 40 42 | 41 59 43 | 70 72 44 | 50 93 45 | 59 99 46 | 33 72 47 | 29 55 48 | 56 70 49 | 6 61 50 | 57 83 51 | 41 54 52 | 37 56 53 | 49 88 54 | 22 50 55 | 6 87 56 | 11 14 57 | 56 96 58 | 11 19 59 | 29 39 60 | 44 78 61 | 22 76 62 | 68 87 63 | 13 40 64 | 28 64 65 | 4 85 66 | 30 71 67 | 15 22 68 | 12 14 69 | 36 81 70 | 1 4 71 | 56 67 72 | 79 93 73 | 19 42 74 | 18 94 75 | 89 92 76 | 37 62 77 | 27 97 78 | 39 47 79 | 6 15 80 | 8 43 81 | 11 86 82 | 27 80 83 | 24 88 84 | 36 86 85 | 2 25 86 | 60 96 87 | 11 44 88 | 1 65 89 | 12 47 90 | 5 75 91 | 57 79 92 | 23 55 93 | 28 54 94 | 29 54 95 | 16 57 96 | 8 40 97 | 5 32 98 | 11 85 99 | 1 29 100 | 16 83 101 | 23 45 102 | 31 32 103 | 4 75 104 | 50 77 105 | 61 89 106 | 3 58 107 | 51 97 108 | 16 18 109 | 1 24 110 | 41 91 111 | 83 92 112 | 88 92 113 | 16 99 114 | 53 93 115 | 80 87 116 | 75 90 117 | 4 25 118 | 3 19 119 | 53 71 120 | 66 81 121 | 40 97 122 | 24 69 123 | 40 63 124 | 13 27 125 | 54 70 126 | -------------------------------------------------------------------------------- /cmd/pineconesim/graphs/sim.txt: -------------------------------------------------------------------------------- 1 | a b c d e f g h i j k l m n o p q r s t u v w x y z 2 | f h u r q f l m n v u n 3 | f i j l p q r n v g h 4 | a b r u r s y m v p 5 | p j g h e f s r n k m l -------------------------------------------------------------------------------- /cmd/pineconesim/sequences/api_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "EventSequence": [ 3 | { 4 | "Command": "Debug", 5 | "Data": {} 6 | }, 7 | { 8 | "Command": "Play", 9 | "Data": {} 10 | }, 11 | { 12 | "Command": "Pause", 13 | "Data": {} 14 | }, 15 | { 16 | "Command": "Delay", 17 | "Data": { 18 | "Length": 100 19 | } 20 | }, 21 | { 22 | "Command": "AddNode", 23 | "Data": { 24 | "Name": "Alice", 25 | "NodeType": "Default" 26 | } 27 | }, 28 | { 29 | "Command": "RemoveNode", 30 | "Data": { 31 | "Name": "Alice" 32 | } 33 | }, 34 | { 35 | "Command": "AddPeer", 36 | "Data": { 37 | "Node": "Alice", 38 | "Peer": "Bob" 39 | } 40 | }, 41 | { 42 | "Command": "RemovePeer", 43 | "Data": { 44 | "Node": "Alice", 45 | "Peer": "Bob" 46 | } 47 | }, 48 | { 49 | "Command": "ConfigureAdversaryDefaults", 50 | "Data": { 51 | "Node": "Alice", 52 | "DropRates": { 53 | "Overall": "0", 54 | "Keepalive": "0", 55 | "TreeAnnouncement": "0", 56 | "VirtualSnakeBootstrap": "0", 57 | "WakeupBroadcast": "0", 58 | "OverlayTraffic": "0" 59 | } 60 | } 61 | }, 62 | { 63 | "Command": "ConfigureAdversaryPeer", 64 | "Data": { 65 | "Node": "Alice", 66 | "Peer": "Bob", 67 | "DropRates": { 68 | "Overall": "0", 69 | "Keepalive": "0", 70 | "TreeAnnouncement": "0", 71 | "VirtualSnakeBootstrap": "0", 72 | "WakeupBroadcast": "0", 73 | "OverlayTraffic": "0" 74 | } 75 | } 76 | }, 77 | { 78 | "Command": "StartPings", 79 | "Data": {} 80 | }, 81 | { 82 | "Command": "StopPings", 83 | "Data": {} 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /cmd/pineconesim/sequences/debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "EventSequence": [ 3 | { 4 | "Command": "Debug", 5 | "Data": {} 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /cmd/pineconesim/sequences/example_alice_and_friends.json: -------------------------------------------------------------------------------- 1 | { 2 | "EventSequence": [ 3 | { 4 | "Command": "AddNode", 5 | "Data": { 6 | "Name": "Alice", 7 | "NodeType": "Default" 8 | } 9 | }, 10 | { 11 | "Command": "AddNode", 12 | "Data": { 13 | "Name": "Bob", 14 | "NodeType": "Default" 15 | } 16 | }, 17 | { 18 | "Command": "AddPeer", 19 | "Data": { 20 | "Node": "Alice", 21 | "Peer": "Bob" 22 | } 23 | }, 24 | { 25 | "Command": "Delay", 26 | "Data": { 27 | "Length": 5000 28 | } 29 | }, 30 | { 31 | "Command": "AddNode", 32 | "Data": { 33 | "Name": "Charlie", 34 | "NodeType": "Default" 35 | } 36 | }, 37 | { 38 | "Command": "AddNode", 39 | "Data": { 40 | "Name": "Dan", 41 | "NodeType": "Default" 42 | } 43 | }, 44 | { 45 | "Command": "AddPeer", 46 | "Data": { 47 | "Node": "Charlie", 48 | "Peer": "Dan" 49 | } 50 | }, 51 | { 52 | "Command": "Delay", 53 | "Data": { 54 | "Length": 5000 55 | } 56 | }, 57 | { 58 | "Command": "AddPeer", 59 | "Data": { 60 | "Node": "Bob", 61 | "Peer": "Charlie" 62 | } 63 | }, 64 | { 65 | "Command": "Delay", 66 | "Data": { 67 | "Length": 5000 68 | } 69 | }, 70 | { 71 | "Command": "RemoveNode", 72 | "Data": { 73 | "Name": "Alice" 74 | } 75 | }, 76 | { 77 | "Command": "RemoveNode", 78 | "Data": { 79 | "Name": "Bob" 80 | } 81 | }, 82 | { 83 | "Command": "RemoveNode", 84 | "Data": { 85 | "Name": "Charlie" 86 | } 87 | }, 88 | { 89 | "Command": "RemoveNode", 90 | "Data": { 91 | "Name": "Dan" 92 | } 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /cmd/pineconesim/sequences/example_drop_snek_proto.json: -------------------------------------------------------------------------------- 1 | { 2 | "EventSequence": [ 3 | { 4 | "Command": "RemoveNode", 5 | "Data": { 6 | "Name": "Alice" 7 | } 8 | }, 9 | { 10 | "Command": "RemoveNode", 11 | "Data": { 12 | "Name": "Bob" 13 | } 14 | }, 15 | { 16 | "Command": "RemoveNode", 17 | "Data": { 18 | "Name": "Dan" 19 | } 20 | }, 21 | { 22 | "Command": "RemoveNode", 23 | "Data": { 24 | "Name": "Mallory" 25 | } 26 | }, 27 | { 28 | "Command": "AddNode", 29 | "Data": { 30 | "Name": "Alice", 31 | "NodeType": "Default" 32 | } 33 | }, 34 | { 35 | "Command": "AddNode", 36 | "Data": { 37 | "Name": "Bob", 38 | "NodeType": "Default" 39 | } 40 | }, 41 | { 42 | "Command": "AddNode", 43 | "Data": { 44 | "Name": "Dan", 45 | "NodeType": "Default" 46 | } 47 | }, 48 | { 49 | "Command": "AddNode", 50 | "Data": { 51 | "Name": "Mallory", 52 | "NodeType": "GeneralAdversary" 53 | } 54 | }, 55 | { 56 | "Command": "ConfigureAdversaryDefaults", 57 | "Data": { 58 | "Node": "Mallory", 59 | "DropRates": { 60 | "Overall": "0", 61 | "Keepalive": "0", 62 | "TreeAnnouncement": "0", 63 | "VirtualSnakeBootstrap": "100", 64 | "WakeupBroadcast": "100", 65 | "OverlayTraffic": "100" 66 | } 67 | } 68 | }, 69 | { 70 | "Command": "AddPeer", 71 | "Data": { 72 | "Node": "Alice", 73 | "Peer": "Bob" 74 | } 75 | }, 76 | { 77 | "Command": "AddPeer", 78 | "Data": { 79 | "Node": "Alice", 80 | "Peer": "Mallory" 81 | } 82 | }, 83 | { 84 | "Command": "ConfigureAdversaryPeer", 85 | "Data": { 86 | "Node": "Mallory", 87 | "Peer": "Dan", 88 | "DropRates": { 89 | "Overall": "0", 90 | "Keepalive": "0", 91 | "TreeAnnouncement": "0", 92 | "VirtualSnakeBootstrap": "0", 93 | "WakeupBroadcast": "0", 94 | "OverlayTraffic": "0" 95 | } 96 | } 97 | }, 98 | { 99 | "Command": "AddPeer", 100 | "Data": { 101 | "Node": "Dan", 102 | "Peer": "Mallory" 103 | } 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | type APIEventMessageID int 18 | type APICommandMessageID int 19 | type APIUpdateID int 20 | type APICommandID int 21 | type APINodeType int 22 | 23 | const ( 24 | UnknownEventMsg APIEventMessageID = iota 25 | SimInitialState 26 | SimStateUpdate 27 | ) 28 | 29 | const ( 30 | UnknownCommandMsg APICommandMessageID = iota 31 | SimPlaySequence 32 | ) 33 | 34 | const ( 35 | UnknownUpdate APIUpdateID = iota 36 | SimNodeAdded 37 | SimNodeRemoved 38 | SimPeerAdded 39 | SimPeerRemoved 40 | SimTreeParentUpdated 41 | SimSnakeAscUpdated 42 | SimSnakeDescUpdated 43 | SimTreeRootAnnUpdated 44 | SimSnakeEntryAdded 45 | SimSnakeEntryRemoved 46 | SimPingStateUpdated 47 | SimNetworkStatsUpdated 48 | SimBroadcastReceived 49 | SimBandwidthReport 50 | ) 51 | 52 | const ( 53 | UnknownCommand APICommandID = iota 54 | SimDebug 55 | SimPlay 56 | SimPause 57 | SimDelay 58 | SimAddNode 59 | SimRemoveNode 60 | SimAddPeer 61 | SimRemovePeer 62 | SimConfigureAdversaryDefaults 63 | SimConfigureAdversaryPeer 64 | SimStartPings 65 | SimStopPings 66 | ) 67 | 68 | const ( 69 | UnknownType APINodeType = iota 70 | DefaultNode 71 | GeneralAdversaryNode 72 | ) 73 | 74 | type InitialNodeState struct { 75 | PublicKey string 76 | NodeType APINodeType 77 | RootState RootState 78 | Peers []PeerInfo 79 | TreeParent string 80 | SnakeAsc string 81 | SnakeAscPath string 82 | SnakeDesc string 83 | SnakeDescPath string 84 | SnakeEntries []SnakeRouteEntry 85 | BroadcastsReceived []BroadcastEntry 86 | BandwidthReports []BandwidthSnapshot 87 | } 88 | 89 | type RootState struct { 90 | Root string 91 | AnnSequence uint64 92 | AnnTime uint64 93 | Coords []uint64 94 | } 95 | 96 | type PeerInfo struct { 97 | ID string 98 | Port int 99 | } 100 | 101 | type SnakeRouteEntry struct { 102 | EntryID string 103 | PeerID string 104 | } 105 | 106 | type BroadcastEntry struct { 107 | PeerID string 108 | Time uint64 109 | } 110 | 111 | type SimEventMsg struct { 112 | UpdateID APIUpdateID 113 | Event SimEvent 114 | } 115 | 116 | type InitialStateMsg struct { 117 | MsgID APIEventMessageID 118 | Nodes map[string]InitialNodeState 119 | End bool 120 | BWReportingInterval int 121 | } 122 | 123 | type StateUpdateMsg struct { 124 | MsgID APIEventMessageID 125 | Event SimEventMsg 126 | } 127 | 128 | type SimCommandSequenceMsg struct { 129 | MsgID APICommandMessageID 130 | Events []SimCommandMsg 131 | } 132 | 133 | type SimCommandMsg struct { 134 | MsgID APICommandID 135 | Event interface{} 136 | } 137 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/events.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import "github.com/matrix-org/pinecone/router/events" 18 | 19 | type SimEvent interface { 20 | isEvent() 21 | } 22 | 23 | type NodeAdded struct { 24 | Node string 25 | PublicKey string 26 | NodeType int 27 | RouteCount int 28 | } 29 | 30 | // Tag NodeAdded as an Event 31 | func (e NodeAdded) isEvent() {} 32 | 33 | type NodeRemoved struct { 34 | Node string 35 | } 36 | 37 | // Tag NodeRemoved as an Event 38 | func (e NodeRemoved) isEvent() {} 39 | 40 | type PeerAdded struct { 41 | Node string 42 | Peer string 43 | Port uint64 44 | } 45 | 46 | // Tag PeerAdded as an Event 47 | func (e PeerAdded) isEvent() {} 48 | 49 | type PeerRemoved struct { 50 | Node string 51 | Peer string 52 | } 53 | 54 | // Tag PeerRemoved as an Event 55 | func (e PeerRemoved) isEvent() {} 56 | 57 | type TreeParentUpdate struct { 58 | Node string 59 | Peer string 60 | Prev string 61 | } 62 | 63 | // Tag TreeParentUpdate as an Event 64 | func (e TreeParentUpdate) isEvent() {} 65 | 66 | type SnakeAscUpdate struct { 67 | Node string 68 | Peer string 69 | Prev string 70 | PathID string 71 | } 72 | 73 | // Tag SnakeAscUpdate as an Event 74 | func (e SnakeAscUpdate) isEvent() {} 75 | 76 | type SnakeDescUpdate struct { 77 | Node string 78 | Peer string 79 | Prev string 80 | PathID string 81 | } 82 | 83 | // Tag SnakeDescUpdate as an Event 84 | func (e SnakeDescUpdate) isEvent() {} 85 | 86 | type TreeRootAnnUpdate struct { 87 | Node string 88 | Root string // Root Public Key 89 | Sequence uint64 90 | Time uint64 // Unix Time 91 | Coords []uint64 92 | } 93 | 94 | // Tag TreeRootAnnUpdate as an Event 95 | func (e TreeRootAnnUpdate) isEvent() {} 96 | 97 | type PingStateUpdate struct { 98 | Enabled bool 99 | Active bool 100 | } 101 | 102 | // Tag PingStateUpdate as an Event 103 | func (e PingStateUpdate) isEvent() {} 104 | 105 | type NetworkStatsUpdate struct { 106 | PathConvergence uint64 107 | AverageStretch float64 108 | } 109 | 110 | // Tag NetworkStatsUpdate as an Event 111 | func (e NetworkStatsUpdate) isEvent() {} 112 | 113 | type SnakeEntryAdded struct { 114 | Node string 115 | EntryID string 116 | PeerID string 117 | } 118 | 119 | // Tag SnakeEntryAdded as an Event 120 | func (e SnakeEntryAdded) isEvent() {} 121 | 122 | type SnakeEntryRemoved struct { 123 | Node string 124 | EntryID string 125 | } 126 | 127 | // Tag SnakeEntryRemoved as an Event 128 | func (e SnakeEntryRemoved) isEvent() {} 129 | 130 | type BroadcastReceived struct { 131 | Node string 132 | PeerID string 133 | Time uint64 134 | } 135 | 136 | // Tag BroadcastReceived as an Event 137 | func (e BroadcastReceived) isEvent() {} 138 | 139 | type BandwidthReport struct { 140 | Node string 141 | Bandwidth BandwidthSnapshot 142 | } 143 | 144 | // Tag BandwidthReport as an Event 145 | func (e BandwidthReport) isEvent() {} 146 | 147 | type eventHandler struct { 148 | node string 149 | ch <-chan events.Event 150 | } 151 | 152 | func (h eventHandler) Run(quit <-chan bool, sim *Simulator) { 153 | for { 154 | select { 155 | case <-quit: 156 | return 157 | case event := <-h.ch: 158 | switch e := event.(type) { 159 | case events.PeerAdded: 160 | sim.handlePeerAdded(h.node, e.PeerID, int(e.Port)) 161 | case events.PeerRemoved: 162 | sim.handlePeerRemoved(h.node, e.PeerID, int(e.Port)) 163 | case events.TreeParentUpdate: 164 | sim.handleTreeParentUpdate(h.node, e.PeerID) 165 | case events.SnakeDescUpdate: 166 | sim.handleSnakeDescUpdate(h.node, e.PeerID, "") // TODO: do we need the path ID? 167 | case events.TreeRootAnnUpdate: 168 | sim.handleTreeRootAnnUpdate(h.node, e.Root, e.Sequence, e.Time, e.Coords) 169 | case events.SnakeEntryAdded: 170 | sim.handleSnakeEntryAdded(h.node, e.EntryID, e.PeerID) 171 | case events.SnakeEntryRemoved: 172 | sim.handleSnakeEntryRemoved(h.node, e.EntryID) 173 | case events.BroadcastReceived: 174 | sim.handleBroadcastReceived(h.node, e.PeerID, e.Time) 175 | case events.BandwidthReport: 176 | sim.handleBandwidthReport(h.node, e.CaptureTime, e.Peers) 177 | default: 178 | sim.log.Println("Unhandled event!") 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "math" 19 | ) 20 | 21 | func (sim *Simulator) ReportDistance(a, b string, l int64) { 22 | sim.distsMutex.Lock() 23 | defer sim.distsMutex.Unlock() 24 | 25 | if _, ok := sim.dists[a]; !ok { 26 | sim.dists[a] = map[string]*Distance{} 27 | } 28 | 29 | if _, ok := sim.dists[a][b]; !ok { 30 | sim.dists[a][b] = &Distance{} 31 | } 32 | 33 | sim.dists[a][b].Observed = l 34 | } 35 | 36 | func (sim *Simulator) UpdateRealDistances() { 37 | sim.distsMutex.Lock() 38 | defer sim.distsMutex.Unlock() 39 | 40 | for from := range sim.nodes { 41 | for to := range sim.nodes { 42 | if _, ok := sim.dists[from]; !ok { 43 | sim.dists[from] = map[string]*Distance{} 44 | } 45 | 46 | if _, ok := sim.dists[from][to]; !ok { 47 | sim.dists[from][to] = &Distance{} 48 | } 49 | 50 | a, _ := sim.graph.GetMapping(from) 51 | b, _ := sim.graph.GetMapping(to) 52 | if a != -1 && b != -1 { 53 | path, err := sim.graph.Shortest(a, b) 54 | 55 | if err == nil { 56 | sim.dists[from][to].Real = path.Distance 57 | } else { 58 | sim.dists[from][to].Real = math.MaxInt64 59 | } 60 | } 61 | } 62 | } 63 | 64 | sim.State.Act(nil, func() { 65 | sim.distsMutex.Lock() 66 | defer sim.distsMutex.Unlock() 67 | sim.State._updateExpectedBroadcasts(sim.dists) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/links.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "time" 21 | 22 | "github.com/matrix-org/pinecone/router" 23 | "github.com/matrix-org/pinecone/util" 24 | ) 25 | 26 | func (sim *Simulator) ConnectNodes(a, b string) error { 27 | if a == b { 28 | return fmt.Errorf("invalid node pair, a node cannot peer with iself") 29 | } 30 | sim.nodesMutex.RLock() 31 | na := sim.nodes[a] 32 | nb := sim.nodes[b] 33 | sim.nodesMutex.RUnlock() 34 | if na == nil || nb == nil { 35 | return fmt.Errorf("invalid node pair, one or both of the nodes don't exist") 36 | } 37 | 38 | sim.wiresMutex.RLock() 39 | wa := sim.wires[a][b] 40 | wb := sim.wires[b][a] 41 | sim.wiresMutex.RUnlock() 42 | if wa != nil || wb != nil { 43 | return fmt.Errorf("already connected") 44 | } 45 | 46 | register := func(conn net.Conn) { 47 | sim.wiresMutex.Lock() 48 | defer sim.wiresMutex.Unlock() 49 | if sim.wires[a] == nil { 50 | sim.wires[a] = map[string]net.Conn{} 51 | } 52 | sim.wires[a][b] = conn 53 | } 54 | 55 | if sim.sockets { 56 | c, err := net.DialTCP(na.l.Addr().Network(), nil, na.ListenAddr) 57 | if err != nil { 58 | return fmt.Errorf("net.Dial: %w", err) 59 | } 60 | if err := c.SetNoDelay(true); err != nil { 61 | panic(err) 62 | } 63 | sc := &util.SlowConn{Conn: c, ReadJitter: 5 * time.Millisecond} 64 | if _, err := nb.Connect( 65 | sc, 66 | router.ConnectionKeepalives(true), 67 | router.ConnectionPeerType(router.PeerTypeRemote), 68 | ); err != nil { 69 | return fmt.Errorf("nb.AuthenticatedConnect: %w", err) 70 | } 71 | register(sc) 72 | } else { 73 | pa, pb := net.Pipe() 74 | pa = &util.SlowConn{Conn: pa, ReadJitter: 1 * time.Millisecond} 75 | pb = &util.SlowConn{Conn: pb, ReadJitter: 1 * time.Millisecond} 76 | go func() { 77 | if _, err := na.Connect( 78 | pa, 79 | router.ConnectionPublicKey(nb.PublicKey()), 80 | router.ConnectionKeepalives(false), 81 | router.ConnectionPeerType(router.PeerTypePipe), 82 | ); err != nil { 83 | return 84 | } 85 | }() 86 | go func() { 87 | if _, err := nb.Connect( 88 | pb, 89 | router.ConnectionPublicKey(na.PublicKey()), 90 | router.ConnectionKeepalives(false), 91 | router.ConnectionPeerType(router.PeerTypePipe), 92 | ); err != nil { 93 | return 94 | } 95 | }() 96 | register(pa) 97 | } 98 | 99 | sim.log.Printf("Connected node %q to node %q\n", a, b) 100 | return nil 101 | } 102 | 103 | func (sim *Simulator) DisconnectNodes(a, b string) error { 104 | sim.wiresMutex.RLock() 105 | wire := sim.wires[a][b] 106 | firstIndex, secondIndex := a, b 107 | if wire == nil { 108 | wire = sim.wires[b][a] 109 | firstIndex, secondIndex = b, a 110 | } 111 | sim.wiresMutex.RUnlock() 112 | if wire == nil { 113 | return fmt.Errorf("nodes not connected") 114 | } 115 | 116 | sim.wiresMutex.Lock() 117 | sim.wires[firstIndex][secondIndex] = nil 118 | sim.wiresMutex.Unlock() 119 | 120 | return wire.Close() 121 | } 122 | 123 | func (sim *Simulator) DisconnectAllPeers(disconnectNode string) { 124 | sim.wiresMutex.Lock() 125 | defer sim.wiresMutex.Unlock() 126 | 127 | nodeWires := sim.wires[disconnectNode] 128 | for i, conn := range nodeWires { 129 | if conn != nil { 130 | _ = conn.Close() 131 | sim.wires[disconnectNode][i] = nil 132 | } 133 | } 134 | 135 | for node, peers := range sim.wires { 136 | for peer, conn := range peers { 137 | if peer == disconnectNode { 138 | if conn != nil { 139 | _ = conn.Close() 140 | sim.wires[node][peer] = nil 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/pathfind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | ) 22 | 23 | func (sim *Simulator) Ping(from, to string) (uint16, time.Duration, error) { 24 | fromnode := sim.nodes[from] 25 | tonode := sim.nodes[to] 26 | success := false 27 | 28 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 29 | defer cancel() 30 | 31 | defer func() { 32 | sim.pathConvergenceMutex.Lock() 33 | if _, ok := sim.pathConvergence[from]; !ok { 34 | sim.pathConvergence[from] = map[string]bool{} 35 | } 36 | sim.pathConvergence[from][to] = success 37 | sim.pathConvergenceMutex.Unlock() 38 | }() 39 | 40 | hops, rtt, err := fromnode.Ping(ctx, tonode.PublicKey()) 41 | if err != nil { 42 | return 0, 0, fmt.Errorf("fromnode.Ping: %w", err) 43 | } 44 | 45 | success = true 46 | sim.ReportDistance(from, to, int64(hops)) 47 | return hops, rtt, nil 48 | } 49 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/ping.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "crypto/ed25519" 19 | "encoding/binary" 20 | "fmt" 21 | 22 | "github.com/matrix-org/pinecone/types" 23 | ) 24 | 25 | type PingType uint8 26 | 27 | const ( 28 | Ping PingType = iota 29 | Pong 30 | ) 31 | 32 | const pingPreamble = "pineping" 33 | const pingSize = len(pingPreamble) + (ed25519.PublicKeySize * 2) + 3 34 | 35 | type PingPayload struct { 36 | pingType PingType 37 | origin types.PublicKey 38 | destination types.PublicKey 39 | hops uint16 40 | } 41 | 42 | func (p *PingPayload) MarshalBinary(buffer []byte) (int, error) { 43 | if len(buffer) < pingSize { 44 | return 0, fmt.Errorf("buffer too small") 45 | } 46 | offset := copy(buffer, []byte(pingPreamble)) 47 | buffer[offset] = uint8(p.pingType) 48 | offset++ 49 | binary.BigEndian.PutUint16(buffer[offset:offset+2], p.hops) 50 | offset += 2 51 | offset += copy(buffer[offset:], p.origin[:ed25519.PublicKeySize]) 52 | offset += copy(buffer[offset:], p.destination[:ed25519.PublicKeySize]) 53 | return offset, nil 54 | } 55 | 56 | func (p *PingPayload) UnmarshalBinary(buffer []byte) (int, error) { 57 | if len(buffer) < pingSize { 58 | return 0, fmt.Errorf("buffer too small") 59 | } 60 | if string(buffer[:len(pingPreamble)]) != pingPreamble { 61 | return 0, fmt.Errorf("not a ping") 62 | } 63 | offset := len(pingPreamble) 64 | p.pingType = PingType(buffer[offset]) 65 | offset++ 66 | p.hops = binary.BigEndian.Uint16(buffer[offset : offset+2]) 67 | offset += 2 68 | offset += copy(p.origin[:], buffer[offset:]) 69 | offset += copy(p.destination[:], buffer[offset:]) 70 | return offset, nil 71 | } 72 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/realpaths.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "github.com/RyanCarrier/dijkstra" 19 | ) 20 | 21 | func (sim *Simulator) GenerateNetworkGraph() { 22 | sim.log.Println("Building graph") 23 | sim.graph = dijkstra.NewGraph() 24 | sim.maps = make(map[string]int) 25 | for n := range sim.nodes { 26 | sim.maps[n] = sim.graph.AddMappedVertex(n) 27 | } 28 | for a, aa := range sim.wires { 29 | for b := range aa { 30 | if err := sim.graph.AddMappedArc(a, b, 1); err != nil { 31 | panic(err) 32 | } 33 | if err := sim.graph.AddMappedArc(b, a, 1); err != nil { 34 | panic(err) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cmd/pineconesim/simulator/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 simulator 16 | 17 | import ( 18 | "net" 19 | ) 20 | 21 | type Node struct { 22 | SimRouter 23 | l *net.TCPListener // nolint:structcheck,unused 24 | ListenAddr *net.TCPAddr 25 | Type APINodeType 26 | } 27 | 28 | type Distance struct { 29 | Real int64 30 | Observed int64 31 | } 32 | 33 | type RouterConfig struct { 34 | HopLimiting bool 35 | } 36 | -------------------------------------------------------------------------------- /cmd/pineconesim/ui/main.js: -------------------------------------------------------------------------------- 1 | import { graph } from "./modules/graph.js"; 2 | import { APIEventMessageID, APICommandMessageID, APIUpdateID, APICommandID, 3 | ConnectToServer, SendToServer } from "./modules/server-api.js"; 4 | import { SetPingToolState, UpdateBandwidthGraphData } from "./modules/ui.js"; 5 | 6 | function handleSimMessage(msg) { 7 | // console.log(msg.data); 8 | switch(msg.data.MsgID) { 9 | case APIEventMessageID.InitialState: 10 | for (let [key, value] of Object.entries(msg.data.Nodes)) { 11 | graph.addNode(key, value.PublicKey, value.NodeType); 12 | graph.updateRootAnnouncement(key, value.RootState.Root, value.RootState.AnnSequence, value.RootState.AnnTime, value.RootState.Coords); 13 | 14 | if (value.Peers) { 15 | for (let i = 0; i < value.Peers.length; i++) { 16 | graph.addPeer(key, value.Peers[i].ID, value.Peers[i].Port); 17 | } 18 | } 19 | 20 | if (value.SnakeAsc && value.SnakeAscPath) { 21 | graph.setSnekAsc(key, value.SnakeAsc, "", value.SnakeAscPath); 22 | } 23 | if (value.SnakeDesc && value.SnakeDescPath) { 24 | graph.setSnekDesc(key, value.SnakeDesc, "", value.SnakeDescPath); 25 | } 26 | 27 | if (value.TreeParent) { 28 | graph.setTreeParent(key, value.TreeParent, ""); 29 | } 30 | 31 | if (value.SnakeEntries) { 32 | for (let i = 0; i < value.SnakeEntries.length; i++) { 33 | graph.addSnakeEntry(key, value.SnakeEntries[i].EntryID, value.SnakeEntries[i].PeerID); 34 | } 35 | } 36 | 37 | if (value.BroadcastsReceived) { 38 | for (let i = 0; i < value.BroadcastsReceived.length; i++) { 39 | graph.addBroadcast(key, value.BroadcastsReceived[i].PeerID, value.BroadcastsReceived[i].Time); 40 | } 41 | } 42 | 43 | if (value.BandwidthReports) { 44 | for (let i = 0; i < value.BandwidthReports.length; i++) { 45 | graph.addBandwidthReport(key, value.BandwidthReports[i]); 46 | } 47 | } 48 | } 49 | 50 | if (msg.data.End === true) { 51 | let bwIntervalSeconds = msg.data.BWReportingInterval; 52 | UpdateBandwidthGraphData(bwIntervalSeconds); 53 | setInterval(function() { UpdateBandwidthGraphData(bwIntervalSeconds); }, bwIntervalSeconds * 1000); 54 | graph.startGraph(); 55 | 56 | console.log("Finished receiving initial state from server. Listening for further updates..."); 57 | } 58 | break; 59 | case APIEventMessageID.StateUpdate: 60 | let event = msg.data.Event.Event; 61 | switch(msg.data.Event.UpdateID) { 62 | case APIUpdateID.NodeAdded: 63 | graph.addNode(event.Node, event.PublicKey, event.NodeType); 64 | break; 65 | case APIUpdateID.NodeRemoved: 66 | graph.removeNode(event.Node); 67 | break; 68 | case APIUpdateID.PeerAdded: 69 | graph.addPeer(event.Node, event.Peer, event.Port); 70 | break; 71 | case APIUpdateID.PeerRemoved: 72 | graph.removePeer(event.Node, event.Peer); 73 | break; 74 | case APIUpdateID.TreeParentUpdated: 75 | graph.setTreeParent(event.Node, event.Peer, event.Prev); 76 | break; 77 | case APIUpdateID.SnakeAscUpdated: 78 | graph.setSnekAsc(event.Node, event.Peer, event.Prev, event.PathID); 79 | break; 80 | case APIUpdateID.SnakeDescUpdated: 81 | graph.setSnekDesc(event.Node, event.Peer, event.Prev, event.PathID); 82 | break; 83 | case APIUpdateID.TreeRootAnnUpdated: 84 | graph.updateRootAnnouncement(event.Node, event.Root, event.Sequence, event.Time, event.Coords); 85 | break; 86 | case APIUpdateID.SnakeEntryAdded: 87 | graph.addSnakeEntry(event.Node, event.EntryID, event.PeerID); 88 | break; 89 | case APIUpdateID.SnakeEntryRemoved: 90 | graph.removeSnakeEntry(event.Node, event.EntryID); 91 | break; 92 | case APIUpdateID.PingStateUpdated: 93 | SetPingToolState(event.Enabled, event.Active); 94 | break; 95 | case APIUpdateID.NetworkStatsUpdated: 96 | graph.updateNetworkStats(event.PathConvergence, event.AverageStretch); 97 | break; 98 | case APIUpdateID.BroadcastReceived: 99 | graph.addBroadcast(event.Node, event.PeerID, event.Time); 100 | break; 101 | case APIUpdateID.BandwidthReport: 102 | graph.addBandwidthReport(event.Node, event.Bandwidth); 103 | break; 104 | } 105 | break; 106 | default: 107 | console.log("Unhandled message ID: " + msg.data.MsgID); 108 | break; 109 | } 110 | }; 111 | 112 | ConnectToServer({url: window.origin.replace("http", "ws") + '/ws'}, handleSimMessage); 113 | 114 | function selectStartingNetwork() { 115 | let selectionTabs = document.getElementsByClassName("netselect"); 116 | var hash = window.location.hash.substr(1); 117 | if (hash != "") { 118 | for (let i = 0; i < selectionTabs.length; i++) { 119 | if (selectionTabs[i].id.includes(hash)) { 120 | selectionTabs[i].click(); 121 | break; 122 | } 123 | } 124 | } 125 | } 126 | selectStartingNetwork(); 127 | -------------------------------------------------------------------------------- /cmd/pineconesim/ui/modules/server-api.js: -------------------------------------------------------------------------------- 1 | export const APIEventMessageID = { 2 | Unknown: 0, 3 | InitialState: 1, 4 | StateUpdate: 2, 5 | }; 6 | 7 | export const APICommandMessageID = { 8 | Unknown: 0, 9 | PlaySequence: 1, 10 | }; 11 | 12 | export const APIUpdateID = { 13 | Unknown: 0, 14 | NodeAdded: 1, 15 | NodeRemoved: 2, 16 | PeerAdded: 3, 17 | PeerRemoved: 4, 18 | TreeParentUpdated: 5, 19 | SnakeAscUpdated: 6, 20 | SnakeDescUpdated: 7, 21 | TreeRootAnnUpdated: 8, 22 | SnakeEntryAdded: 9, 23 | SnakeEntryRemoved: 10, 24 | PingStateUpdated: 11, 25 | NetworkStatsUpdated: 12, 26 | BroadcastReceived: 13, 27 | BandwidthReport: 14, 28 | }; 29 | 30 | export const APICommandID = { 31 | Unknown: 0, 32 | Debug: 1, 33 | Play: 2, 34 | Pause: 3, 35 | Delay: 4, 36 | AddNode: 5, 37 | RemoveNode: 6, 38 | AddPeer: 7, 39 | RemovePeer: 8, 40 | ConfigureAdversaryDefaults: 9, 41 | ConfigureAdversaryPeer: 10, 42 | StartPings: 11, 43 | StopPings: 12, 44 | }; 45 | 46 | export const APINodeType = { 47 | Unknown: 0, 48 | Default: 1, 49 | GeneralAdversary: 2, 50 | }; 51 | 52 | var serverWorker; 53 | 54 | export function ConnectToServer(url, handler) { 55 | if (!serverWorker) { 56 | console.log("Connecting to server at: " + url.url); 57 | serverWorker = new Worker("ui/websocket-worker.js"); 58 | serverWorker.onmessage = handler; 59 | serverWorker.postMessage(url); 60 | } 61 | } 62 | 63 | export function SendToServer(msg) { 64 | if (serverWorker) { 65 | serverWorker.postMessage(msg); 66 | } 67 | } 68 | 69 | export function ConvertNodeTypeToString(nodeType) { 70 | let val = "Unknown"; 71 | switch(nodeType) { 72 | case APINodeType.Default: 73 | val = "Default"; 74 | break; 75 | case APINodeType.GeneralAdversary: 76 | val = "General Adversary"; 77 | break; 78 | } 79 | 80 | return val; 81 | } 82 | -------------------------------------------------------------------------------- /cmd/pineconesim/ui/websocket-worker.js: -------------------------------------------------------------------------------- 1 | let websocketWorker; 2 | let serverUrl; 3 | let connectedToServer = false; 4 | 5 | class WebsocketWorker { 6 | state = { 7 | socket: null, 8 | }; 9 | 10 | constructor(url) { 11 | this.state = { 12 | socket: null, 13 | }; 14 | 15 | serverUrl = url; 16 | connectedToServer = false; 17 | } 18 | 19 | Connect() { 20 | this.socketConnect(); 21 | } 22 | 23 | socketConnect() { 24 | if (this.state) { 25 | this.state.socket = new WebSocket(serverUrl); 26 | this.state.socket.onopen = this.socketOpenListener; 27 | this.state.socket.onmessage = this.socketMessageListener; 28 | this.state.socket.onclose = this.socketCloseListener; 29 | this.state.socket.onerror = this.socketErrorListener; 30 | } 31 | } 32 | 33 | socketMessageListener(e) { 34 | let msg = JSON.parse(e.data); 35 | postMessage(msg); 36 | } 37 | 38 | socketOpenListener(e) { 39 | console.log('Connected to server'); 40 | connectedToServer = true; 41 | } 42 | 43 | socketErrorListener(e) { 44 | console.error(e); 45 | connectedToServer = false; 46 | } 47 | 48 | socketCloseListener(e) { 49 | if (connectedToServer) { 50 | console.log('Disconnected from server'); 51 | connectedToServer = false; 52 | } 53 | } 54 | 55 | SendCommandSequence(data) { 56 | if (this.state.socket && connectedToServer) { 57 | console.log("Sending commands to server: " + JSON.stringify(data)); 58 | this.state.socket.send(JSON.stringify(data)); 59 | } else { 60 | console.error("Failed sending command to server: Not connected"); 61 | } 62 | } 63 | } 64 | 65 | onmessage = e => { 66 | if (e.data.hasOwnProperty('url')){ 67 | websocketWorker = new WebsocketWorker(e.data.url); 68 | websocketWorker.Connect(); 69 | } else if (e.data.hasOwnProperty('Events')){ 70 | websocketWorker.SendCommandSequence(e.data); 71 | } else { 72 | console.error('Received unhandled message: ' + e); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /connections/manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 connections 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "math" 21 | "net" 22 | "net/http" 23 | "strings" 24 | "time" 25 | 26 | "github.com/Arceliar/phony" 27 | "github.com/matrix-org/pinecone/router" 28 | "github.com/matrix-org/pinecone/types" 29 | "nhooyr.io/websocket" 30 | ) 31 | 32 | const interval = time.Second * 5 33 | 34 | type ConnectionManager struct { 35 | phony.Inbox 36 | ctx context.Context 37 | cancel context.CancelFunc 38 | router *router.Router 39 | client *http.Client 40 | ws *websocket.DialOptions 41 | _staticPeers map[string]*connectionAttempts 42 | _connectedPeers map[string]struct{} 43 | } 44 | 45 | type connectionAttempts struct { 46 | attempts float64 47 | next time.Time 48 | } 49 | 50 | func NewConnectionManager(r *router.Router, client *http.Client) *ConnectionManager { 51 | ctx, cancel := context.WithCancel(context.Background()) 52 | m := &ConnectionManager{ 53 | ctx: ctx, 54 | cancel: cancel, 55 | router: r, 56 | client: client, 57 | ws: &websocket.DialOptions{ 58 | HTTPClient: client, 59 | }, 60 | _staticPeers: map[string]*connectionAttempts{}, 61 | _connectedPeers: map[string]struct{}{}, 62 | } 63 | if m.ws.HTTPClient == nil { 64 | m.ws.HTTPClient = http.DefaultClient 65 | } 66 | time.AfterFunc(interval, m._worker) 67 | return m 68 | } 69 | 70 | func (m *ConnectionManager) _connect(uri string) { 71 | result := func(err error) { 72 | attempts := m._staticPeers[uri] 73 | if attempts == nil { 74 | return 75 | } 76 | if err != nil { 77 | attempts.attempts++ 78 | until := time.Second * time.Duration(math.Exp2(attempts.attempts)) 79 | if until > time.Hour { 80 | until = time.Hour 81 | } 82 | attempts.next = time.Now().Add(until) 83 | } else { 84 | attempts.attempts = 0 85 | attempts.next = time.Now() 86 | } 87 | } 88 | ctx, cancel := context.WithTimeout(m.ctx, interval) 89 | defer cancel() 90 | var parent net.Conn 91 | switch { 92 | case strings.HasPrefix(uri, "ws://"): 93 | fallthrough 94 | case strings.HasPrefix(uri, "wss://"): 95 | c, _, err := websocket.Dial(ctx, uri, m.ws) 96 | if err != nil { 97 | result(err) 98 | return 99 | } 100 | parent = websocket.NetConn(m.ctx, c, websocket.MessageBinary) 101 | default: 102 | var err error 103 | dialer := net.Dialer{ 104 | Timeout: interval, 105 | } 106 | parent, err = dialer.DialContext(ctx, "tcp", uri) 107 | if err != nil { 108 | result(err) 109 | return 110 | } 111 | } 112 | if parent == nil { 113 | result(fmt.Errorf("no parent connection")) 114 | return 115 | } 116 | _, err := m.router.Connect( 117 | parent, 118 | router.ConnectionZone("static"), 119 | router.ConnectionPeerType(router.PeerTypeRemote), 120 | router.ConnectionURI(uri), 121 | ) 122 | result(err) 123 | } 124 | 125 | func (m *ConnectionManager) _worker() { 126 | for k := range m._connectedPeers { 127 | delete(m._connectedPeers, k) 128 | } 129 | for _, peerInfo := range m.router.Peers() { 130 | m._connectedPeers[peerInfo.URI] = struct{}{} 131 | } 132 | 133 | for peer, attempts := range m._staticPeers { 134 | if _, ok := m._connectedPeers[peer]; !ok && time.Now().After(attempts.next) { 135 | uri := peer 136 | m.Act(nil, func() { 137 | m._connect(uri) 138 | }) 139 | } 140 | } 141 | 142 | select { 143 | case <-m.ctx.Done(): 144 | default: 145 | time.AfterFunc(interval, m._worker) 146 | } 147 | } 148 | 149 | func (m *ConnectionManager) AddPeer(uri string) { 150 | phony.Block(m, func() { 151 | if _, existing := m._staticPeers[uri]; existing { 152 | return 153 | } 154 | m._staticPeers[uri] = &connectionAttempts{ 155 | attempts: 0, 156 | next: time.Now(), 157 | } 158 | m._connect(uri) 159 | }) 160 | } 161 | 162 | func (m *ConnectionManager) RemovePeer(uri string) { 163 | phony.Block(m, func() { 164 | if _, existing := m._staticPeers[uri]; !existing { 165 | return 166 | } 167 | delete(m._staticPeers, uri) 168 | for _, peerInfo := range m.router.Peers() { 169 | if peerInfo.URI == uri { 170 | m.router.Disconnect(types.SwitchPortID(peerInfo.Port), fmt.Errorf("removing peer")) 171 | } 172 | } 173 | }) 174 | } 175 | 176 | func (m *ConnectionManager) RemovePeers() { 177 | phony.Block(m, func() { 178 | for _, peerInfo := range m.router.Peers() { 179 | if _, ok := m._staticPeers[peerInfo.URI]; ok { 180 | m.router.Disconnect(types.SwitchPortID(peerInfo.Port), fmt.Errorf("removing peer")) 181 | } 182 | } 183 | for uri := range m._staticPeers { 184 | delete(m._staticPeers, uri) 185 | } 186 | }) 187 | } 188 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem "github-pages", "~> 226", group: :jekyll_plugins 3 | group :jekyll_plugins do 4 | gem "jekyll-feed", "~> 0.15.1" 5 | end 6 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Pinecone 2 | description: >- 3 | Peer-to-Peer overlay router for the Matrix protocol! 4 | baseurl: "/pinecone" # the subpath of your site, e.g. /blog 5 | url: "" 6 | twitter_username: matrixdotorg 7 | github_username: matrix-org 8 | remote_theme: just-the-docs/just-the-docs 9 | plugins: 10 | - jekyll-feed 11 | aux_links: 12 | "GitHub": 13 | - "//github.com/matrix-org/pinecone" 14 | aux_links_new_tab: true 15 | sass: 16 | sass_dir: _sass 17 | style: compressed 18 | exclude: 19 | -------------------------------------------------------------------------------- /docs/_sass/custom/custom.scss: -------------------------------------------------------------------------------- 1 | footer.site-footer { 2 | opacity: 10%; 3 | } 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | nav_exclude: true 4 | --- 5 | 6 | # Pinecone 7 | 8 | This wiki contains some information about the Pinecone design: 9 | 10 | * **[Introduction](introduction.md)** 11 | * **[Spanning Tree](spanning_tree.md)** 12 | * **[Virtual Snake](virtual_snake.md)** 13 | * **[Peer Management](peer_management.md)** 14 | 15 | You can also join us in our Matrix room dedicated to P2P Matrix: 16 | 17 | * **[#p2p:matrix.org](https://matrix.to/#/#p2p:matrix.org)** for p2p matrix discussion and support 18 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | has_children: true 4 | nav_order: 1 5 | permalink: /introduction 6 | --- 7 | 8 | # Introduction 9 | 10 | Pinecone is an implemenatation of the Sequentially Networked Edwards Key (SNEK) name-independent routing scheme that combines two network topologies in order to build an efficient and scalable overlay network. 11 | 12 | Pinecone is: 13 | 14 | * **Name-independent**: 15 | * Node identifiers are not related to the physical network topology; 16 | * They do not change as the node moves around the network; 17 | * The address space is considered to be global — there is no subnetting; 18 | 19 | * **Built up of equal players**: 20 | * Nodes will forward traffic on behalf of other nodes; 21 | * There are no specific points of centralisation; 22 | * A node can join a larger network by connecting to any other node; 23 | 24 | * **Self-healing**: 25 | * Tolerates node mobility far better than Batman-adv, Babel and many other routing protocols; 26 | * Alternative paths will be discovered automatically if possible when nodes go offline or connections are lost. 27 | 28 | * **Transport-agnostic**: 29 | * The only requirements for a peering today are that it is stream-oriented and reliable; 30 | * Any connection medium that can offer these semantics is appropriate for a peering, including TCP over regular IP networks, WebSockets, Bluetooth L2CAP (with infinite retransmit) or RFCOMM etc. 31 | 32 | Note, however, that Pinecone is: 33 | 34 | * **Not anonymous**: 35 | * Anonymity networks make significant trade-offs in order to achieve anonymity, both in overall complexity and in path/routing cost, which are unacceptable in resource-constrained environments; 36 | * It is not a goal of the Matrix project to guarantee anonymity — at best Matrix can provide pseudonymity and the same is true with Pinecone public keys; 37 | 38 | * **Not strongly resistant to traffic analysis**: 39 | * Although traffic packet contents will be encrypted end-to-end by default, the source and destination fields in the packet headers are visible to intermediate nodes; 40 | * In some cases, source addresses could be sealed/encrypted, although this is not implemented today; 41 | 42 | * **Not fully resilient against malicious nodes**: 43 | * If a node peers with malicious nodes which (for instance) drop traffic, then connectivity will inevitably be disrupted. This can never be fully mitigated (for instance, if you are in a 2 node network and the other node is malicious, there is nothing you can do) — but work is ongoing to detect and route around malicious behaviour where possible. 44 | 45 | The Pinecone routing scheme effectively is built up of two major components: 46 | 47 | * A **global spanning tree**: 48 | * Provides efficient, low-stretch and strictly loop-free routing; 49 | * Effective means of exchanging path setup messages, even before SNEK bootstrap has taken place; 50 | 51 | * A **virtual snake** (or **SNEK**) — a single-linked linear routing topology: 52 | * Provides resilient public key-based routing across the overlay network. 53 | -------------------------------------------------------------------------------- /docs/introduction/1_network_formation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Network Formation 3 | parent: Introduction 4 | nav_order: 1 5 | permalink: /introduction/network_formation 6 | --- 7 | 8 | # Network Formation 9 | 10 | Strictly speaking, a Pinecone network is formed every time two or more nodes connect to each other. Any two networks that are joined together by a common node will merge into a single larger network. 11 | 12 | Even though the Pinecone address space is considered to be global in scope, it is important to note that there is no true “single” Pinecone network. It is possible for multiple disjoint and disconnected Pinecone network segments to exist. 13 | 14 | This does mean that if every node globally is connected somehow to the same mesh, Pinecone will converge on a single global network and all nodes will be routable to each other as a result. 15 | 16 | It is possible to create private/closed networks within trusted environments by enforcing mutual authentication of peering connections, although this is an implementation-specific detail and not specified here. This may be desirable in some particularly sensitive settings to ensure that trusted nodes do not participate in untrusted networks and that untrusted nodes do not participate in trusted networks. 17 | -------------------------------------------------------------------------------- /docs/introduction/2_node_anatomy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Node Anatomy 3 | parent: Introduction 4 | nav_order: 2 5 | permalink: /introduction/node_anatomy 6 | --- 7 | 8 | # Node Anatomy 9 | 10 | A Pinecone node is effectively a form of user-space hybrid router/switch. The switch has a number of “ports” which can connect to other Pinecone nodes using stream-oriented and reliable transports (such as TCP connections). 11 | 12 | Each port is numbered. There is no requirement for a Pinecone node to have a specific number of ports. However, switch port 0 is always reserved for the local Pinecone router. Traffic going to and from the local node will always use this port. Port number assignment is an implementation-specific detail, although it typically makes sense to always assign the lowest free port number. 13 | 14 | The router maintains state that allows it to participate in the network, including: 15 | 16 | * Information about all directly connected peers, including their public key, last tree announcement received, how they are connected to us etc; 17 | * Which of the node’s directly connected peers is our chosen parent in the tree, if any; 18 | * A routing table, containing information about SNEK paths that have been set up through this node by other nodes; 19 | * Information about our descending keyspace neighbour — that is, which node has the next lowest (descending) key to the node’s own; 20 | * A sequence number, used when operating as a root node and sending the nodes own root announcements into the network; 21 | * Maintenance timers for tree and SNEK maintenance. 22 | -------------------------------------------------------------------------------- /docs/introduction/3_frame_forwarding.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Frame Forwarding 3 | parent: Introduction 4 | nav_order: 3 5 | permalink: /introduction/frame_forwarding 6 | --- 7 | 8 | # Frame Forwarding 9 | 10 | When receiving an incoming data frame on a port, the switch then uses the correct lookup method for the frame type to determine which port to forward the frame to. 11 | 12 | Pinecone separates frames into two types: **protocol frames** and **traffic frames**. 13 | 14 | Protocol frames often have specific rules governing their behaviour, including inspecting and processing the protocol control messages at intermediate or destination hops, and which routing scheme should be used to forward them onwards. 15 | 16 | Traffic frames, on the other hand, are always forwarded using SNEK routing or tree routing and are not required to be otherwise inspected by an intermediate node. 17 | 18 | If a suitable next-hop is identified, the frame will be forwarded to the chosen next-hop peer. 19 | 20 | ## Hybrid Traffic Routing 21 | 22 | **Traffic frames** are forwarded using a combination of both SNEK routing and tree routing. This is done to reduce the overall network stretch while still providing a reliable transport mechanism during times of high network churn. 23 | 24 | Traffic is initially routed using SNEK routing to the destination and the frame header is populated with the sending node's tree coordinates. Upon reaching the destination, the receiving node caches the sending node's tree coordinates. 25 | 26 | When sending traffic, a node will check for matching cached tree coordinates and populate that information in the frame header if possible. The presence of destination coordinates in a traffic frame lets other nodes know to attempt forwarding using tree routing. 27 | 28 | When forwarding traffic frames using tree routing, if any node along the path fails to find a suitable next-hop then the destination coordinates should be removed from the header and SNEK forwarding should be attempted to reach the destination. 29 | 30 | To assist with adjusting to changing network conditions, individual cached coordinates should be removed if they ever become too old. Also, all cached coordinates should be removed if the tree root changes. 31 | 32 | ## Watermarks 33 | 34 | In the case of **traffic frames** there is also a watermark used to help detect routing loops. Watermarks only factor into the equation when next-hops are selected from the SNEK routing table as these routes can become stale much more quickly than route information obtained through the global spanning tree. The watermark ensures that forward progress towards a given key is being made and that a packet will never be forwarded onto a path that is worse than the path selected at the last hop. If any node along the path does not know of either the same best destination node or a closer destination node, then the packet has reached a location where further forwarding could result in routing loops. 35 | 36 | Frames should be dropped if the path watermark (derived from the path key and bootstrap sequence) of the chosen next-hop is worse than the watermark on the received frame. A watermark is defined as being worse if either of the following conditions is met: 37 | - The new watermark has a higher public key than the existing watermark; 38 | - The new watermark has the same public key but a lower sequence number than the existing watermark; 39 | 40 | Before forwarding the frame to the next-hop, if the chosen next-hop was selected using the snake routing table then the watermark on the frame should be updated with the path watermark of the next-hop. 41 | -------------------------------------------------------------------------------- /docs/peer_management.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Peer Management 3 | has_children: true 4 | nav_order: 4 5 | permalink: /peer 6 | --- 7 | 8 | # Peer Management 9 | 10 | Pinecone nodes must be able to respond to changing network conditions, with peers connecting and disconnecting at any moment. A node should do the following things when peers connect or disconnect. 11 | -------------------------------------------------------------------------------- /docs/peer_management/1_peer_connects.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Peer Connects 3 | parent: Peer Management 4 | nav_order: 1 5 | permalink: /peer/connect 6 | --- 7 | 8 | # Peer Connects 9 | 10 | When a new peer connects to a node, the node should immediately send the latest root update from the chosen parent. If no chosen parent is available or the node is acting as a root, the node should send its own root update immediately. 11 | -------------------------------------------------------------------------------- /docs/peer_management/2_peer_disconnects.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Peer Disconnects 3 | parent: Peer Management 4 | nav_order: 2 5 | permalink: /peer/disconnect 6 | --- 7 | 8 | # Peer Disconnects 9 | 10 | A Pinecone node must immediately remove any state related to a peer that disconnects, including the last received root announcement from that peer. 11 | 12 | If the chosen parent is the disconnected peer, the node must re-run the parent selection algorithm immediately, either selecting a new parent (with the equivalent **Root public key** and **Root sequence**) or by becoming a root node and waiting for a stronger update from a peer. 13 | 14 | If the disconnected peer appears in any entry in the routing table, as either the **Source port** or **Destination port**, the entry should be removed from the routing table. 15 | 16 | If the disconnected port is the **Source port** of the descending node entry, the descending entry should be cleared. 17 | -------------------------------------------------------------------------------- /docs/spanning_tree.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spanning Tree 3 | has_children: true 4 | nav_order: 2 5 | permalink: /tree 6 | --- 7 | 8 | # Spanning Tree 9 | 10 | The spanning tree provides network participants with name-dependent identifier routing which is used before nodes have fully bootstrapped. It also acts as a synchronisation primitive as root announcements are periodically flooded through the network. 11 | -------------------------------------------------------------------------------- /docs/spanning_tree/1_root_node.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Root Node 3 | parent: Spanning Tree 4 | nav_order: 1 5 | permalink: tree/root_node 6 | --- 7 | 8 | # Root Node 9 | 10 | Pinecone's spanning tree is a rooted tree, meaning there is a single root node at any given time for a given network. It is the responsibility of the root node to send out root announcements into the network at a specific interval. 11 | 12 | Since Pinecone networks are typically dynamic in nature, root selection must take place automatically and without user intervention, so as to avoid deliberate points of centralisation. In order to do this, a form of network-wide election takes place, where the node with the numerically highest ed25519 public key will win. 13 | 14 | If the current root node disappears from the network or otherwise stops sending root announcements (due to dropped peerings), another election will take place, eventually settling on the next highest key. 15 | 16 | A node considers itself to be a root node if it does not have a chosen parent. 17 | -------------------------------------------------------------------------------- /docs/spanning_tree/2_root_announcements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Root Announcements 3 | parent: Spanning Tree 4 | nav_order: 2 5 | permalink: /tree/root_announcements 6 | --- 7 | 8 | # Root Announcements 9 | 10 | It is necessary for the functioning of a Pinecone network for all nodes to hear an announcement from the same root node so that all nodes can agree on the same tree-space coordinate system for routing bootstrap and setup messages. 11 | 12 | A root announcement contains the following fields: 13 | 14 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 24 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 40 | 42 | 43 | 44 | 46 | 47 | 48 | 50 | 51 | 52 | 54 | 56 | 57 | 58 | 60 | 61 | 62 | 64 | 65 |
Root public key 17 |
Root sequence 21 |
Signature 1 25 | Signing public key 27 |
Destination port number 31 |
Signature covering all fields upto this signature 35 |
Signature 2 39 | Signing public key 41 |
Destination port number 45 |
Signature covering all fields upto this signature 49 |
Signature …n 53 | Signing public key 55 |
Destination port number 59 |
Signature covering all fields upto this signature 63 |
66 | 67 | #### Sequence number 68 | 69 | The sequence number is a monotonically increasing unsigned integer, starting at 0. Since root announcements are flooded through the network, it is the goal for all nodes to eventually agree on the same **Root public key** and **Sequence number** following a root update. 70 | 71 | The sequence number **must** be increased when the root node wishes to send out a new root announcement by reaching the announcement time interval. 72 | 73 | The sequence number **must not** be increased when repeating the previous update to peers for any other reason, and must not be increased by any node that is not the root node before repeating the updates to their peers. 74 | 75 | #### Signatures 76 | 77 | Before sending the root announcement to a peer, it should sign the update, appending the signature to the end of the announcement. Doing so allows any node receiving an root announcement to ensure that none of the ancestors have forged earlier signatures within the update, and to more reliably detect loops within the tree (if a signature incorrectly appears within a given update more than once). 78 | 79 | Note that the **Destination port number** field must include the port number of the peer that the update is being sent to. Therefore if you are repeating the update to 6 peers, you will need to create 6 separate signed updates. 80 | -------------------------------------------------------------------------------- /docs/spanning_tree/3_sending_root_announcements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sending Root Announcements 3 | parent: Spanning Tree 4 | nav_order: 3 5 | permalink: /tree/sending_root_announcements 6 | --- 7 | 8 | # Sending Root Announcements 9 | 10 | It is the responsibility of the root node to send a new root announcement on a regular interval (typically every **30 minutes**). To do this, it should create a new empty announcement, populating the **Root public key** field with the node’s own public key and the **Sequence number**. 11 | 12 | Each node should maintain their own local sequence number, which is used only when sending out root announcements with their own key as the **Root public key**, ensuring that the sequence number increments only when a new announcement is generated (and not when repeating an update due to changes in the tree). 13 | 14 | It is the responsibility of other non-root nodes to sign and repeat good root announcements received from their chosen parent to all directly peered nodes (including repeating the update to the chosen parent) at the time that it is received, although they must not attempt to modify the update in any other way (for example, by trying to modify the **Root public key**, **Root sequence** or any of the existing **Signature** fields). 15 | 16 | Nodes must not repeat bad root announcements or any root announcements arriving from other peers that are not the chosen parent. 17 | -------------------------------------------------------------------------------- /docs/spanning_tree/4_handling_root_announcements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Handling Root Announcements 3 | parent: Spanning Tree 4 | nav_order: 4 5 | permalink: /tree/handling_root_announcements 6 | --- 7 | 8 | # Handling Root Announcements 9 | 10 | When a node receives a root announcement from a direct peer, it should do the following: 11 | 12 | 1. Perform sanity checks on the update; 13 | 2. Store the received update against the peer in memory; 14 | 3. Decide whether to re-parent; 15 | 16 | If a root announcement is received from any peer during the 1 second reparent wait interval, it should be sanity-checked (step 1) and stored (step 2) but should not be processed any further. This is to ensure that many updates arriving quickly from direct peers will not result in a flood of root announcements being sent from the node in rapid succession, which overall reduces the load on the network and prevents the tree rebuilding excessively. 17 | 18 | ## Sanity checks 19 | 20 | The following sanity checks must be performed on all incoming root announcement updates: 21 | 22 | 1. The update must be well-formed, with no missing fields or unexpected values; 23 | 2. There must be at least one signature; 24 | 3. The first **Signing public key** must match the **Root public key**; 25 | 4. The last **Signing public key** must match the public key of your direct peer; 26 | 5. None of the signature entries should have a **Destination port number** of 0; 27 | 6. The update must not contain any loops, that is that the update must not contain the same public key in the signatures more than once; 28 | 7. If a root update has been received from this peer before, and the **Root public key** is equal to the last update, the **Root sequence** must be greater than or equal to the last update. 29 | 30 | If any of these conditions fail, the update is considered to be invalid, the update should be dropped and the peer that sent us this announcement should be disconnected as a result of the error. 31 | 32 | ## Storing root announcements 33 | 34 | When storing a root announcement for a given peer, the following information should be kept: 35 | 36 | - The announcement itself; 37 | - The time the announcement was received; 38 | - The order in which the announcement was received; 39 | - The order of received root announcements must be global across all peers; 40 | 41 | ## Deciding to re-parent 42 | 43 | Once the sanity checks have passed, if the update came from the currently selected parent, perform the following checks in order: 44 | 45 | 1. If the reparent wait timer is active, do nothing and stop processing; 46 | 2. If any of the **Signatures** entries in the update contain the node’s own public key, implying that our parent has suddenly decided to choose us as their parent instead: 47 | 1. Become a root node, sending an announcement to all of your direct peers notifying them of this fact; 48 | 2. Start a 1 second reparent wait timer, after which the parent selection algorithm will run; 49 | 3. If the **Root public key** is lower than the last root announcement from our chosen parent, implying that either the previous root node has disappeared or the parent is notifying us of bad news: 50 | 1. Become a root node, sending an announcement to all of your direct peers notifying them of this fact; 51 | 2. Start a 1 second reparent wait timer, after which the parent selection algorithm will run; 52 | 4. If the **Root public key** and the **Root sequence** fields are both equal to those in the last root announcement from our chosen parent, implying that the parent is notifying us of bad news: 53 | 1. Become a root node, sending an announcement to all of your direct peers notifying them of this fact; 54 | 2. Start a 1 second reparent wait timer, after which the parent selection algorithm will run; 55 | 5. If the **Root public key** is higher than the last root announcement from our chosen parent, implying that our parent has learned about a stronger root: 56 | 1. Send a tree announcement with the new update to all directly connected peers; 57 | 6. If the **Root public key** is equal to the last root announcement but the **Root sequence** is higher than the last root announcement: 58 | 1. Send a tree announcement with the new update to all directly connected peers; 59 | 60 | Otherwise, if the update arrived from another peer that is not our chosen parent, perform the following checks in order: 61 | 62 | 1. If the reparent wait timer is active, do nothing and stop processing; 63 | 2. If any of the **Signatures** entries in the update contain the node’s own public key, implying that the peer has chosen us a parent, do nothing and stop processing; 64 | 3. If the **Root public key** is higher than the last root announcement from our chosen parent: 65 | 1. Set the node’s chosen parent to this specific peer; 66 | 2. Send a tree announcement with the new update to all directly connected peers; 67 | 4. If the **Root public key** is lower than the last root announcement from our chosen parent: 68 | 1. Send a tree announcement back to this peer only with the last root announcement from our chosen parent; 69 | 5. In any other case not matched by the above: 70 | 1. Run the parent selection algorithm. 71 | -------------------------------------------------------------------------------- /docs/spanning_tree/5_parent_selection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Parent Selection 3 | parent: Spanning Tree 4 | nav_order: 5 5 | permalink: /tree/parent_selection 6 | --- 7 | 8 | # Parent Selection 9 | 10 | With the exception of the root node, which cannot by definition have a parent, each node on the network must choose one of its peered nodes to be its “parent”. The chosen parent node will influence the node’s coordinates. 11 | 12 | In the case that a non-root node has only a single peering, that peered node will always be the chosen parent. In the case there are multiple peerings, the parent selection algorithm will determine which the most suitable node is. 13 | 14 | The parent selection algorithm runs as follows: 15 | 16 | 1. Start with the current **Root public key** and **Root sequence** as the best key and best sequence, and the best candidate as an empty field; 17 | 2. Iterate through all directly connected peers, checking all of the following conditions in order: 18 | 1. Skip the peer and move onto the next if the last root announcement received from this peer exceeds the announcement timeout interval, which is typically **45 minutes**; 19 | 2. Skip the peer and move onto the next if the last root announcement received from this peer includes our own public key in the **Signing public key** field of any of the signatures, as this implies that we are receiving an update again that we have already seen and that a routing loop has taken place; 20 | 3. If the **Root public key** of the last peer update is higher than the best key, update the best key and mark this peer as the best candidate; 21 | 4. If the **Root public key** of the last peer update is lower than the best key, skip the peer and move onto the next; 22 | 5. If the **Root sequence** of the last peer update is higher than the best sequence, update the best sequence and mark this peer as the best candidate; 23 | 6. If the **Root sequence** of the last peer update is lower than the best sequence, skip the peer and move onto the next; 24 | 7. As a last resort tie-breaker, mark this peer as the best candidate and move onto the next if the peer's **Root public key** and **Root sequence** match the best key and best sequence, and either: 25 | * This peer's update arrived before the update from the best candidate did, or; 26 | * No other peer has been selected as the best candidate so far. 27 | 28 | If the best candidate is not empty at the end of the loop and the chosen candidate is different to the current chosen parent, mark the best candidate as our chosen parent and then send root announcement updates to all directly connected peers. 29 | 30 | If the best candidate is still empty at the end of the algorithm, none of the connected peers were suitable candidates and therefore the node should become a root node instead, emptying the chosen parent field and then sending a root announcement to all directly connected peers with its own public key as the **Root public key**. 31 | 32 | Directly connected peers are likely to handle the sudden update to a weaker root key as bad news, therefore any nodes that were previously children of this node will respond accordingly. 33 | -------------------------------------------------------------------------------- /docs/spanning_tree/6_coordinates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Coordinates 3 | parent: Spanning Tree 4 | nav_order: 6 5 | permalink: /tree/coordinates 6 | --- 7 | 8 | # Coordinates 9 | 10 | A node’s coordinates describe the location of the node on the spanning tree. In effect, the coordinates are the path from the root node down to a given node, with each number representing the downward port number at each hop. 11 | 12 | For example, coordinates `[1 4 2 4]` describe the following path: starting via the root node’s port 1, followed by the next node’s port 4, followed by the next node’s port 2 and then, finally, the destination node’s parent’s port 4. 13 | 14 | The coordinates of the node are calculated by taking the chosen parent’s last root announcement and concatenating all of the **Destination port number** values in order. 15 | 16 | With that in mind, a root node, which has no chosen parent, will have a zero-length set of coordinates `[]` and a node’s coordinates will change each time it either selects a new parent, or receives a root announcement update from the parent with a different path described in the signatures. 17 | -------------------------------------------------------------------------------- /docs/spanning_tree/7_root_election.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Root Election 3 | parent: Spanning Tree 4 | nav_order: 7 5 | permalink: /tree/root_election 6 | --- 7 | 8 | # Root Election 9 | 10 | When a node first comes online, it has no prior knowledge about the state of the network and therefore does not have a chosen parent. Therefore each node that comes online and joins the network will start off considering itself to be a root node. 11 | 12 | When the first peer connection is made, the node should send its root announcement to the newly connected peer and wait for the remote peer to send their announcement back. One of two things will happen: 13 | 14 | 1. Our key will be stronger than the remote side’s root key (in which case we will remain as a root node, they will have received good news about a new stronger root and will re-run the parent selection algorithm accordingly), or: 15 | 2. The root announcement that they send us will contain a stronger root key than our own public key (which we will consider to be good news) which will cause us to run parent selection. 16 | 17 | In a network which is entirely starting up from cold, or in the case of the previous root node disappearing/going offline, the process of agreeing on a new root is iterative. Nodes will run the parent selection algorithm, finding the strongest root key, notifying their peers, which may cause them to run the parent selection algorithm, announcing their strongest chosen root to their peers, and so on, until the network eventually settles on the strongest key. 18 | -------------------------------------------------------------------------------- /docs/spanning_tree/8_next_hop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Next Hop Calculation 3 | parent: Spanning Tree 4 | nav_order: 8 5 | permalink: /tree/next_hop 6 | --- 7 | 8 | # Next Hop Calculation 9 | 10 | When using tree routing to route towards a certain set of coordinates, a simple distance formula is used. To calculate the distance between coordinates A and B: 11 | 12 | 1. Calculate the length of the common prefix of both sets of coordinates; 13 | 2. Add the length of coordinates A to the length of coordinates B; 14 | 3. Subtract the length of the common prefix multiplied by 2. 15 | 16 | As an illustrative example, where A is `[1 3 5 3 4]` and B is `[1 3 5 7 6 1]`: 17 | 18 | 1. Both A and B start with `[1 3 5]`, therefore the common prefix length is 3; 19 | 2. A has a length of 5, B has a length of 6; 20 | 3. (5 + 6) - (3 ✕ 2) results in a total distance of 5 between A and B. 21 | 22 | The next-hop selection for tree routing is as follows: 23 | 24 | 1. Start with the distance from our own coordinates to the destination coordinates as the best distance, and the best candidate as an empty field; 25 | 2. If the best distance is already 0 then the frame has arrived at its intended destination coordinates. Pass the frame to the local router and stop here, otherwise; 26 | 3. Iterate through all directly connected peers, checking the following conditions in order: 27 | 1. Skip the peer and move onto the next if: 28 | 1. The peer has not sent us a root announcement yet; 29 | 2. The peer is the peer that sent us the frame that we are trying to forward (we will never route the frame back to where it came from as this would create a routing loop); 30 | 3. The **Root public key** of the peer’s last root announcement does not match that of our chosen parent’s last announcement; 31 | 4. The **Root sequence** of the peer’s last root announcement does not match that of our chosen parent’s last announcement; 32 | 2. Calculate the distance from the peer’s coordinates to the destination coordinates; 33 | 3. If the calculated distance is less than the best distance, update the best distance and mark the peer as the best candidate; 34 | 4. Skip the peer and move onto the next if the calculated distance is greater than the best distance; 35 | 5. If the best candidate is not empty and the peer’s last root announcement was received sooner than that of the best candidate, mark the peer as the best candidate. 36 | 37 | If the best candidate is still empty at the end of the algorithm, there is no suitable next-hop candidate that will take the frame closer to its destination. A node must never route a tree-routed packet to a peer that will take it further away from the destination. 38 | -------------------------------------------------------------------------------- /docs/virtual_snake.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Virtual Snake 3 | has_children: true 4 | nav_order: 3 5 | permalink: /snake 6 | --- 7 | 8 | # Virtual Snake 9 | 10 | The virtual snake provides the name-independent identifier routing which efficiently provides robust routing for overlay traffic. It works by effectively arranging all of the nodes on the network into a line, ordered by their public keys, and building paths between keyspace neighbours. 11 | -------------------------------------------------------------------------------- /docs/virtual_snake/1_neighbours.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Snake Neighbours 3 | parent: Virtual Snake 4 | nav_order: 1 5 | permalink: /virtual_snake/neighbours 6 | --- 7 | 8 | # Snake Neighbours 9 | 10 | Each node in the topology has a reference to a descending path. The descending path is the path on the network that leads to the next closest public key to our own in the descending direction (the next lowest key). 11 | 12 | There is only one exception to this rule: the node with the lowest key on the network will have only an ascending path (as there is no lower key to build a descending path). 13 | 14 | For the descending path, a node should store the following information: 15 | 16 | 1. The **Origin public key**, noting which node initiated the path creation; 17 | 2. The **Source port**, where the Bootstrap message arrived from; 18 | 3. The **Destination port**, where the Bootstrap message was forwarded to next (if applicable); 19 | 4. The **Last seen** time, noting when the entry was populated; 20 | 5. The **Root public key** and **Root sequence** that the path was set up with. 21 | 6. The **Watermark public key** and **Watermark sequence** that the path was set up with. 22 | -------------------------------------------------------------------------------- /docs/virtual_snake/2_bootstrapping.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bootstrapping 3 | parent: Virtual Snake 4 | nav_order: 2 5 | permalink: /virtual_snake/bootstrapping 6 | --- 7 | 8 | # Bootstrapping 9 | 10 | Bootstrapping is the process of joining the snake topology. Bootstrapping takes place every 5 seconds and takes place in two steps: 11 | 12 | 1. The bootstrapping node sends a bootstrap message into the network, with their own public key as the “destination” key, which will be routed to the nearest keyspace neighbour; 13 | 2. The nearest keyspace neighbour will add the bootstrapping node as their descending neighbour. 14 | 15 | The bootstrap message contains the following fields: 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 27 | 28 | 29 | 31 | 32 |
Bootstrap Sequence 20 |
Root public key 24 | Root sequence 26 |
Source signature 30 |
33 | 34 | The **Source signature** is an ed25519 signature covering the **Bootstrap Sequence**, **Root public key** and **Root Sequence** by concatenating them together and signing the result. This enables the remote side to verify that the bootstrap was genuinely initiated by the sending node and has not been forged. 35 | 36 | Bootstraps will travel through the network, forwarded **using SNEK routing with bootstrap rules**, towards the destination key, until they arrive at the node that is closest to the destination key. The forwarding logic specifically will not deliver bootstrap messages to the actual destination key, so the bootstrap message will eventually arrive at a “dead end” at the next closest key. 37 | -------------------------------------------------------------------------------- /docs/virtual_snake/3_bootstraps.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Handling Bootstraps 3 | parent: Virtual Snake 4 | nav_order: 3 5 | permalink: /virtual_snake/bootstraps 6 | --- 7 | 8 | # Handling Bootstrap Messages 9 | 10 | Once the bootstrap message arrives at a dead end, the node will update it's descending node entry if it makes sense to do so (ie. same **Root public key** and **Root sequence** and a closer key than the previous descending entry or an update from our existing descending node). 11 | 12 | Before doing anything, the node must ensure that the signature in the **Source signature** field is valid by checking against the **Destination public key**. If the signature is invalid, the bootstrap message should be silently dropped and not processed any further. 13 | 14 | The node should ensure that the **Root public key** and **Root sequence** of the bootstrap message match those of the most recent root announcement from our chosen parent, if any, or the node’s own public key and sequence number if the node is currently acting as a root node. If this is not true, the bootstrap message should be silently dropped and not processed any further. 15 | 16 | Each node along the bootstrapping path should install the bootstrapping node into their routing table. 17 | 18 | ## Install route into routing table 19 | 20 | Regardless of whether the bootstrap message is considered to have arrived at its intended destination (there is no closer node to route to) or not, a bootstrap message should result in the route being installed into the routing table of each node handling the message. 21 | 22 | Before installing the bootstrapping node into the routing table, each node should compare the bootstrap sequence against any existing entry for the bootstrapping node. If an entry exists and the new bootstrap sequence number isn't higher than the current entry, then the bootstrap should be dropped and not processed any further. The only reason to see a bootstrap with a lower or equal sequence number to a bootstrap the node has seen before is if there is a routing loop present. 23 | 24 | To install the route into the routing table, the node should either create a new entry or overwrite the existing entry and: 25 | 26 | 1. Copy the **Origin public key** into the virtual snake index; 27 | 2. Copy the bootstrap **Root public key** and **Root sequence** into the appropriate fields; 28 | 3. Populate the **Last seen time** with the current time; 29 | 4. Populate the **Source port** with a reference to the port that the setup message was received from; 30 | 5. Populate the **Destination port** with a reference to the chosen next-hop port, unless the bootstrap message has reached its intended destination, in which case this field should remain empty; 31 | 6. Copy the setup **Watermark public key** and **Watermark sequence** into the appropriate fields. 32 | -------------------------------------------------------------------------------- /docs/virtual_snake/5_maintenance.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Routine Maintenance 3 | parent: Virtual Snake 4 | nav_order: 5 5 | permalink: /virtual_snake/maintenance 6 | --- 7 | 8 | # Routine Maintenance 9 | 10 | At a specified interval, typically every 1 second, the node should run the following checks: 11 | 12 | 1. If the descending node entry has expired, that is, the time since the **Last seen** entry has passed 10 seconds, remove the entry; 13 | 2. If the descending node entry has different root information, remove the entry; 14 | 3. Remove any routing table entries that are older than 10 seconds. 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/matrix-org/pinecone 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 7 | github.com/RyanCarrier/dijkstra v1.1.0 8 | github.com/gorilla/websocket v1.5.0 9 | github.com/quic-go/quic-go v0.37.4 10 | github.com/vishvananda/netlink v1.1.0 11 | go.uber.org/atomic v1.9.0 12 | golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 13 | golang.org/x/net v0.14.0 14 | golang.org/x/sys v0.11.0 15 | golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 16 | nhooyr.io/websocket v1.8.7 17 | ) 18 | 19 | require ( 20 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 21 | github.com/golang/mock v1.6.0 // indirect 22 | github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect 23 | github.com/klauspost/compress v1.15.9 // indirect 24 | github.com/onsi/ginkgo/v2 v2.11.0 // indirect 25 | github.com/quic-go/qtls-go1-20 v0.3.2 // indirect 26 | github.com/stretchr/testify v1.7.0 // indirect 27 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect 28 | golang.org/x/crypto v0.12.0 // indirect 29 | golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect 30 | golang.org/x/mod v0.12.0 // indirect 31 | golang.org/x/tools v0.12.0 // indirect 32 | golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /multicast/platform_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 darwin 16 | // +build darwin 17 | 18 | package multicast 19 | 20 | import "C" 21 | 22 | import ( 23 | "fmt" 24 | "syscall" 25 | 26 | "golang.org/x/sys/unix" 27 | ) 28 | 29 | func (m *Multicast) multicastStarted() { // nolint:unused 30 | 31 | } 32 | 33 | func (m *Multicast) udpOptions(network string, address string, c syscall.RawConn) error { 34 | var reuseport error 35 | //var recvanyif error 36 | control := c.Control(func(fd uintptr) { 37 | reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 38 | // recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) // SO_RECV_ANYIF 39 | }) 40 | 41 | switch { 42 | case reuseport != nil: 43 | return fmt.Errorf("SO_REUSEPORT: %w", reuseport) 44 | //case recvanyif != nil: 45 | // return fmt.Errorf("SO_RECV_ANYIF: %w", recvanyif) 46 | default: 47 | return control 48 | } 49 | } 50 | 51 | func (m *Multicast) tcpOptions(network string, address string, c syscall.RawConn) error { 52 | /* 53 | var recvanyif error 54 | control := c.Control(func(fd uintptr) { 55 | recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) // SO_RECV_ANYIF 56 | }) 57 | 58 | switch { 59 | case recvanyif != nil: 60 | return fmt.Errorf("SO_RECV_ANYIF: %w", recvanyif) 61 | default: 62 | return control 63 | } 64 | */ 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /multicast/platform_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 | // +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows 16 | 17 | package multicast 18 | 19 | import ( 20 | "syscall" 21 | ) 22 | 23 | func (m *Multicast) multicastStarted() { 24 | } 25 | 26 | func (m *Multicast) udpOptions(network string, address string, c syscall.RawConn) error { 27 | return nil 28 | } 29 | 30 | func (m *Multicast) tcpOptions(network string, address string, c syscall.RawConn) error { 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /multicast/platform_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 | // +build linux netbsd freebsd openbsd dragonflybsd 16 | 17 | package multicast 18 | 19 | import ( 20 | "fmt" 21 | "syscall" 22 | 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | func (m *Multicast) multicastStarted() { 27 | } 28 | 29 | func (m *Multicast) udpOptions(network string, address string, c syscall.RawConn) error { 30 | var reuseport error 31 | control := c.Control(func(fd uintptr) { 32 | reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 33 | }) 34 | 35 | switch { 36 | case reuseport != nil: 37 | return fmt.Errorf("SO_REUSEPORT: %w", reuseport) 38 | default: 39 | return control 40 | } 41 | } 42 | 43 | func (m *Multicast) tcpOptions(network string, address string, c syscall.RawConn) error { 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /multicast/platform_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 | // +build windows 16 | 17 | package multicast 18 | 19 | import ( 20 | "fmt" 21 | "syscall" 22 | 23 | "golang.org/x/sys/windows" 24 | ) 25 | 26 | func (m *Multicast) multicastStarted() { 27 | } 28 | 29 | func (m *Multicast) udpOptions(network string, address string, c syscall.RawConn) error { 30 | var reuseport error 31 | control := c.Control(func(fd uintptr) { 32 | reuseport = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) 33 | }) 34 | 35 | switch { 36 | case reuseport != nil: 37 | return fmt.Errorf("SO_REUSEPORT: %w", reuseport) 38 | default: 39 | return control 40 | } 41 | } 42 | 43 | func (m *Multicast) tcpOptions(network string, address string, c syscall.RawConn) error { 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /router/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 !minimal 16 | // +build !minimal 17 | 18 | package router 19 | 20 | import ( 21 | "encoding/hex" 22 | 23 | "github.com/Arceliar/phony" 24 | "github.com/matrix-org/pinecone/router/events" 25 | "github.com/matrix-org/pinecone/types" 26 | ) 27 | 28 | type NeighbourInfo struct { 29 | PublicKey types.PublicKey 30 | } 31 | 32 | type PeerInfo struct { 33 | URI string 34 | Port int 35 | PublicKey string 36 | PeerType int 37 | Zone string 38 | } 39 | 40 | // Subscribe registers a subscriber to this node's events 41 | func (r *Router) Subscribe(ch chan<- events.Event) { 42 | phony.Block(r, func() { 43 | r._subscribers[ch] = &phony.Inbox{} 44 | }) 45 | } 46 | 47 | func (r *Router) Coords() types.Coordinates { 48 | return r.state.coords() 49 | } 50 | 51 | func (r *Router) Peers() []PeerInfo { 52 | var infos []PeerInfo 53 | phony.Block(r.state, func() { 54 | for _, p := range r.state._peers { 55 | if p == nil { 56 | continue 57 | } 58 | infos = append(infos, PeerInfo{ 59 | URI: string(p.uri), 60 | Port: int(p.port), 61 | PublicKey: hex.EncodeToString(p.public[:]), 62 | PeerType: int(p.peertype), 63 | Zone: string(p.zone), 64 | }) 65 | } 66 | }) 67 | return infos 68 | } 69 | 70 | func (r *Router) EnableHopLimiting() { 71 | r._hopLimiting.Store(true) 72 | } 73 | 74 | func (r *Router) DisableHopLimiting() { 75 | r._hopLimiting.Store(false) 76 | } 77 | -------------------------------------------------------------------------------- /router/consts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "math" 19 | "time" 20 | ) 21 | 22 | const portCount = math.MaxUint8 - 1 23 | const trafficBuffer = math.MaxUint8 - 1 24 | 25 | // peerKeepaliveInterval is the frequency at which this 26 | // node will send keepalive packets to other peers if no 27 | // other packets have been sent within the peerKeepaliveInterval. 28 | const peerKeepaliveInterval = time.Second * 3 29 | 30 | // peerKeepaliveTimeout is the amount of time that must 31 | // pass without receiving any packet before we 32 | // will assume that the peer is dead. 33 | const peerKeepaliveTimeout = time.Second * 5 34 | 35 | // announcementInterval is the frequency at which this 36 | // node will send root announcements to other peers. 37 | const announcementInterval = time.Minute * 30 38 | 39 | // announcementTimeout is the amount of time that must 40 | // pass without receiving a root announcement before we 41 | // will assume that the peer is dead. 42 | const announcementTimeout = time.Minute * 45 43 | 44 | // virtualSnakeMaintainInterval is how often we check to 45 | // see if SNEK maintenance needs to be done. 46 | const virtualSnakeMaintainInterval = time.Second 47 | 48 | // virtualSnakeBootstrapInterval is how often we will aim 49 | // to send bootstrap messages into the network. 50 | const virtualSnakeBootstrapInterval = time.Second * 5 51 | 52 | // virtualSnakeNeighExpiryPeriod is how long we'll wait 53 | // to expire a path that hasn't re-bootstrapped. 54 | const virtualSnakeNeighExpiryPeriod = virtualSnakeBootstrapInterval * 2 55 | 56 | // coordsCacheLifetime is how long we'll keep entries in 57 | // the coords cache for switching to tree routing. 58 | const coordsCacheLifetime = time.Minute 59 | 60 | // coordsCacheMaintainInterval is how often we will clean 61 | // out stale entries from the coords cache. 62 | const coordsCacheMaintainInterval = time.Minute 63 | 64 | // wakeupBroadcastInterval is how often we will aim 65 | // to send broadcast messages into the network. 66 | const wakeupBroadcastInterval = time.Minute 67 | 68 | // broadcastExpiryPeriod is how long we'll wait to 69 | // expire a seen broadcast. 70 | const broadcastExpiryPeriod = wakeupBroadcastInterval * 3 71 | 72 | // broadcastFilterTime is how much time must pass 73 | // before we'll accept a new broadcast from this node. 74 | // This helps to prevent broadcasts from flooding the 75 | // network. 76 | const broadcastFilterTime = wakeupBroadcastInterval / 2 77 | -------------------------------------------------------------------------------- /router/events/events.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 events 16 | 17 | import ( 18 | "github.com/matrix-org/pinecone/types" 19 | ) 20 | 21 | type Event interface { 22 | isEvent() 23 | } 24 | 25 | type PeerAdded struct { 26 | Port types.SwitchPortID 27 | PeerID string 28 | } 29 | 30 | // Tag PeerAdded as an Event 31 | func (e PeerAdded) isEvent() {} 32 | 33 | type PeerRemoved struct { 34 | Port types.SwitchPortID 35 | PeerID string 36 | } 37 | 38 | // Tag PeerRemoved as an Event 39 | func (e PeerRemoved) isEvent() {} 40 | 41 | type TreeParentUpdate struct { 42 | PeerID string 43 | } 44 | 45 | // Tag TreeParentUpdate as an Event 46 | func (e TreeParentUpdate) isEvent() {} 47 | 48 | type SnakeDescUpdate struct { 49 | PeerID string 50 | } 51 | 52 | // Tag SnakeDescUpdate as an Event 53 | func (e SnakeDescUpdate) isEvent() {} 54 | 55 | type TreeRootAnnUpdate struct { 56 | Root string // Root Public Key 57 | Sequence uint64 58 | Time uint64 // Unix Time 59 | Coords []uint64 60 | } 61 | 62 | // Tag TreeRootAnnUpdate as an Event 63 | func (e TreeRootAnnUpdate) isEvent() {} 64 | 65 | type SnakeEntryAdded struct { 66 | EntryID string 67 | PeerID string 68 | } 69 | 70 | // Tag SnakeEntryAdded as an Event 71 | func (e SnakeEntryAdded) isEvent() {} 72 | 73 | type SnakeEntryRemoved struct { 74 | EntryID string 75 | } 76 | 77 | // Tag SnakeEntryRemoved as an Event 78 | func (e SnakeEntryRemoved) isEvent() {} 79 | 80 | type BroadcastReceived struct { 81 | PeerID string 82 | Time uint64 83 | } 84 | 85 | // Tag BroadcastReceived as an Event 86 | func (e BroadcastReceived) isEvent() {} 87 | 88 | type PeerBandwidthUsage struct { 89 | Protocol struct { 90 | Rx uint64 91 | Tx uint64 92 | } 93 | Overlay struct { 94 | Rx uint64 95 | Tx uint64 96 | } 97 | } 98 | 99 | type BandwidthReport struct { 100 | CaptureTime uint64 // Unix Time 101 | Peers map[string]PeerBandwidthUsage 102 | } 103 | 104 | // Tag BandwidthReport as an Event 105 | func (e BandwidthReport) isEvent() {} 106 | -------------------------------------------------------------------------------- /router/manhole.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "encoding/json" 19 | "net/http" 20 | "sort" 21 | "time" 22 | 23 | "github.com/Arceliar/phony" 24 | "github.com/matrix-org/pinecone/types" 25 | ) 26 | 27 | type manholeResponse struct { 28 | Public types.PublicKey `json:"public_key"` 29 | Coords types.Coordinates `json:"coords"` 30 | Root *types.Root `json:"root"` 31 | Parent *peer `json:"parent"` 32 | Peers map[string][]manholePeer `json:"peers"` 33 | SNEK struct { 34 | Descending *virtualSnakeEntry `json:"descending"` 35 | Paths []*virtualSnakeEntry `json:"paths"` 36 | } `json:"snek"` 37 | CoordCache map[string]types.Coordinates `json:"coords_cache"` 38 | } 39 | 40 | type manholePeer struct { 41 | Coords types.Coordinates `json:"coords,omitempty"` 42 | Order uint64 `json:"order,omitempty"` 43 | Port types.SwitchPortID `json:"port"` 44 | PeerType ConnectionPeerType `json:"type,omitempty"` 45 | PeerZone ConnectionZone `json:"zone,omitempty"` 46 | PeerURI ConnectionURI `json:"uri,omitempty"` 47 | RXProto uint64 `json:"rx_proto_bytes"` 48 | TXProto uint64 `json:"tx_proto_bytes"` 49 | RXTraffic uint64 `json:"rx_traffic_bytes"` 50 | TXTraffic uint64 `json:"tx_traffic_bytes"` 51 | ProtoQueue queue `json:"proto_queue"` 52 | TrafficQueue queue `json:"traffic_queue"` 53 | } 54 | 55 | func (r *Router) ManholeHandler(w http.ResponseWriter, req *http.Request) { 56 | response := manholeResponse{ 57 | Public: r.public, 58 | Peers: map[string][]manholePeer{}, 59 | } 60 | phony.Block(r.state, func() { 61 | response.Public = r.public 62 | response.Coords = r.state._coords() 63 | response.Parent = r.state._parent 64 | if rootAnn := r.state._rootAnnouncement(); rootAnn != nil { 65 | response.Root = &rootAnn.Root 66 | } 67 | response.CoordCache = map[string]types.Coordinates{} 68 | for k, v := range r.state._coordsCache { 69 | if time.Since(v.lastSeen) > coordsCacheLifetime { 70 | continue 71 | } 72 | response.CoordCache[k.String()] = v.coordinates 73 | } 74 | for _, p := range r.state._peers { 75 | if p == nil || !p.started.Load() { 76 | continue 77 | } 78 | info := manholePeer{ 79 | Port: p.port, 80 | PeerType: p.peertype, 81 | PeerZone: p.zone, 82 | PeerURI: p.uri, 83 | ProtoQueue: p.proto, 84 | TrafficQueue: p.traffic, 85 | } 86 | phony.Block(&p.statistics, func() { 87 | info.RXProto, info.RXTraffic = p.statistics._bytesRxProto, p.statistics._bytesRxTraffic 88 | info.TXProto, info.TXTraffic = p.statistics._bytesTxProto, p.statistics._bytesTxTraffic 89 | }) 90 | if ann := r.state._announcements[p]; ann != nil { 91 | info.Coords = ann.Coords() 92 | info.Order = ann.receiveOrder 93 | } 94 | public := p.public.String() 95 | response.Peers[public] = append(response.Peers[public], info) 96 | } 97 | response.SNEK.Descending = r.state._descending 98 | for _, p := range r.state._table { 99 | response.SNEK.Paths = append(response.SNEK.Paths, p) 100 | } 101 | }) 102 | for _, p := range response.Peers { 103 | sort.Slice(p, func(i, j int) bool { 104 | return p[i].Order < p[j].Order 105 | }) 106 | } 107 | sort.Slice(response.SNEK.Paths, func(i, j int) bool { 108 | return response.SNEK.Paths[i].PublicKey.CompareTo(response.SNEK.Paths[j].PublicKey) < 0 109 | }) 110 | encoder := json.NewEncoder(w) 111 | encoder.SetIndent("", " ") 112 | if err := encoder.Encode(response); err != nil { 113 | w.WriteHeader(500) 114 | return 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /router/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import "github.com/matrix-org/pinecone/types" 18 | 19 | type RouterOptionBlackhole bool 20 | 21 | type RouterOption interface { 22 | isRouterOption() 23 | } 24 | 25 | func (o RouterOptionBlackhole) isRouterOption() {} 26 | 27 | type ConnectionOption interface { 28 | isConnectionOption() 29 | } 30 | 31 | type ConnectionPublicKey types.PublicKey 32 | type ConnectionURI string 33 | type ConnectionZone string 34 | type ConnectionPeerType int 35 | type ConnectionKeepalives bool 36 | 37 | func (w ConnectionPublicKey) isConnectionOption() {} 38 | func (w ConnectionURI) isConnectionOption() {} 39 | func (w ConnectionZone) isConnectionOption() {} 40 | func (w ConnectionPeerType) isConnectionOption() {} 41 | func (w ConnectionKeepalives) isConnectionOption() {} 42 | -------------------------------------------------------------------------------- /router/packetconn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "net" 19 | "time" 20 | 21 | "github.com/Arceliar/phony" 22 | "github.com/matrix-org/pinecone/types" 23 | "go.uber.org/atomic" 24 | ) 25 | 26 | // newLocalPeer returns a new local peer. It should only be called once when 27 | // the router is set up. 28 | func (r *Router) newLocalPeer(blackhole bool) *peer { 29 | peer := &peer{ 30 | router: r, 31 | port: 0, 32 | context: r.context, 33 | cancel: r.cancel, 34 | conn: nil, 35 | zone: "local", 36 | peertype: 0, 37 | public: r.public, 38 | started: *atomic.NewBool(true), 39 | } 40 | if !blackhole { 41 | peer.traffic = newFairFIFOQueue(trafficBuffer, r.log) 42 | } 43 | return peer 44 | } 45 | 46 | // ReadFrom reads the next packet that was delivered to this node over the 47 | // Pinecone network. Only traffic frames will be returned here (not protocol 48 | // frames). The returned address will either be a `types.PublicKey` (if the 49 | // frame was delivered using SNEK routing) or `types.Coordinates` (if the frame 50 | // was delivered using tree routing). 51 | func (r *Router) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 52 | if r.local.traffic == nil { 53 | <-r.local.context.Done() 54 | return 0, nil, nil 55 | } 56 | 57 | var frame *types.Frame 58 | readDeadline := r._readDeadline.Load() 59 | select { 60 | case <-r.local.context.Done(): 61 | r.local.stop(nil) 62 | return 63 | case <-time.After(time.Until(readDeadline)): 64 | return 65 | case frame = <-r.local.traffic.pop(): 66 | // A protocol packet is ready to send. 67 | r.local.traffic.ack() 68 | } 69 | 70 | addr = frame.SourceKey 71 | n = len(frame.Payload) 72 | copy(p, frame.Payload) 73 | return 74 | } 75 | 76 | // WriteTo sends a packet into the Pinecone network. The packet will be sent 77 | // as a traffic packet. The supplied net.Addr will dictate the method used to 78 | // route the packet — the address should be a `types.PublicKey` for SNEK routing 79 | // or `types.Coordinates` for tree routing. Supplying an unsupported address type 80 | // will result in a `*net.AddrError` being returned. 81 | func (r *Router) WriteTo(p []byte, addr net.Addr) (n int, err error) { 82 | timer := time.NewTimer(time.Second * 5) 83 | defer func() { 84 | if !timer.Stop() { 85 | <-timer.C 86 | } 87 | }() 88 | 89 | switch ga := addr.(type) { 90 | case types.PublicKey: 91 | frame := getFrame() 92 | frame.HopLimit = types.MaxHopLimit 93 | frame.Type = types.TypeTraffic 94 | frame.DestinationKey = ga 95 | phony.Block(r.state, func() { 96 | if cached, ok := r.state._coordsCache[ga]; ok && time.Since(cached.lastSeen) < coordsCacheLifetime { 97 | frame.Destination = cached.coordinates 98 | } 99 | }) 100 | frame.Source = r.state.coords() 101 | frame.SourceKey = r.public 102 | frame.Payload = append(frame.Payload[:0], p...) 103 | frame.Watermark = types.VirtualSnakeWatermark{ 104 | PublicKey: types.FullMask, 105 | Sequence: 0, 106 | } 107 | phony.Block(r.state, func() { 108 | _ = r.state._forward(r.local, frame) 109 | }) 110 | return len(p), nil 111 | 112 | default: 113 | err = &net.AddrError{ 114 | Err: "unexpected address type", 115 | Addr: addr.String(), 116 | } 117 | return 118 | } 119 | } 120 | 121 | // LocalAddr returns a net.Addr containing the public key of the node for 122 | // SNEK routing. 123 | func (r *Router) LocalAddr() net.Addr { 124 | return r.PublicKey() 125 | } 126 | 127 | // SetDeadline is not implemented. 128 | func (r *Router) SetDeadline(t time.Time) error { 129 | return nil 130 | } 131 | 132 | func (r *Router) SetReadDeadline(t time.Time) error { 133 | r._readDeadline.Store(t) 134 | return nil 135 | } 136 | 137 | // SetWriteDeadline is not implemented. 138 | func (r *Router) SetWriteDeadline(t time.Time) error { 139 | return nil 140 | } 141 | -------------------------------------------------------------------------------- /router/pools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/matrix-org/pinecone/types" 21 | ) 22 | 23 | var frameBufferPool = &sync.Pool{ 24 | New: func() interface{} { 25 | b := [types.MaxFrameSize]byte{} 26 | return &b 27 | }, 28 | } 29 | 30 | var framePool = &sync.Pool{ 31 | New: func() interface{} { 32 | f := &types.Frame{ 33 | Payload: make([]byte, 0, types.MaxPayloadSize), 34 | } 35 | return f 36 | }, 37 | } 38 | 39 | func getFrame() *types.Frame { 40 | f := framePool.Get().(*types.Frame) 41 | f.Reset() 42 | return f 43 | } 44 | -------------------------------------------------------------------------------- /router/pprof.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | // +build debug 3 | 4 | package router 5 | 6 | // This file only imports pprof if "-tags debug" is supplied during build. 7 | 8 | import ( 9 | _ "net/http/pprof" 10 | ) 11 | -------------------------------------------------------------------------------- /router/queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import "github.com/matrix-org/pinecone/types" 18 | 19 | type queue interface { 20 | queuecount() int 21 | queuesize() int 22 | push(frame *types.Frame) bool 23 | pop() <-chan *types.Frame 24 | ack() 25 | reset() 26 | } 27 | -------------------------------------------------------------------------------- /router/queuefairfifo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "encoding/json" 19 | "math/rand" 20 | "sync" 21 | 22 | "github.com/matrix-org/pinecone/types" 23 | ) 24 | 25 | const fairFIFOQueueSize = 16 26 | 27 | type fairFIFOQueue struct { 28 | log types.Logger 29 | queues map[uint16]chan *types.Frame // queue ID -> frame, map for randomness 30 | num uint16 // how many queues should we have? 31 | count int // how many queued items in total? 32 | n uint16 // which queue did we last iterate on? 33 | offset uint64 // adds an element of randomness to queue assignment 34 | total uint64 // how many packets handled? 35 | dropped uint64 // how many packets dropped? 36 | mutex sync.Mutex 37 | } 38 | 39 | func newFairFIFOQueue(num uint16, log types.Logger) *fairFIFOQueue { 40 | q := &fairFIFOQueue{ 41 | log: log, 42 | offset: rand.Uint64(), 43 | num: num, 44 | } 45 | q.reset() 46 | return q 47 | } 48 | 49 | func (q *fairFIFOQueue) queuecount() int { // nolint:unused 50 | q.mutex.Lock() 51 | defer q.mutex.Unlock() 52 | return q.count 53 | } 54 | 55 | func (q *fairFIFOQueue) queuesize() int { // nolint:unused 56 | return int(q.num) * fairFIFOQueueSize 57 | } 58 | 59 | func (q *fairFIFOQueue) hash(frame *types.Frame) uint16 { 60 | h := q.offset 61 | for _, v := range frame.Source { 62 | h += uint64(v) 63 | } 64 | for _, v := range frame.Destination { 65 | h += uint64(v) 66 | } 67 | for _, v := range frame.SourceKey { 68 | h += uint64(v) 69 | } 70 | for _, v := range frame.DestinationKey { 71 | h += uint64(v) 72 | } 73 | return uint16(h % uint64(q.num)) 74 | } 75 | 76 | func (q *fairFIFOQueue) push(frame *types.Frame) bool { 77 | q.mutex.Lock() 78 | defer q.mutex.Unlock() 79 | var h uint16 80 | if q.count > 0 { 81 | h = q.hash(frame) + 1 82 | } 83 | select { 84 | case q.queues[h] <- frame: 85 | // There is space in the queue 86 | q.count++ 87 | default: 88 | // The queue is full - perform a head drop 89 | <-q.queues[h] 90 | q.dropped++ 91 | if q.count-1 == 0 { 92 | h = 0 93 | } 94 | q.queues[h] <- frame 95 | } 96 | q.total++ 97 | return true 98 | } 99 | 100 | func (q *fairFIFOQueue) reset() { 101 | q.mutex.Lock() 102 | defer q.mutex.Unlock() 103 | q.queues = make(map[uint16]chan *types.Frame, q.num+1) 104 | for i := uint16(0); i <= q.num; i++ { 105 | q.queues[i] = make(chan *types.Frame, fairFIFOQueueSize) 106 | } 107 | } 108 | 109 | func (q *fairFIFOQueue) pop() <-chan *types.Frame { 110 | q.mutex.Lock() 111 | defer q.mutex.Unlock() 112 | switch { 113 | case q.count == 0: 114 | // Nothing has been queued yet — whatever gets queued up 115 | // next will always be in queue 0, so it makes sense to 116 | // return the channel for that queue for now. 117 | fallthrough 118 | case len(q.queues[0]) > 0: 119 | // There's something in queue 0 waiting to be sent. 120 | return q.queues[0] 121 | default: 122 | // Select the next queue that has something waiting. 123 | for i := uint16(0); i <= q.num; i++ { 124 | if q.n = (q.n + 1) % (q.num + 1); q.n == 0 { 125 | continue 126 | } 127 | if queue := q.queues[q.n]; len(queue) > 0 { 128 | return queue 129 | } 130 | } 131 | } 132 | // We shouldn't ever arrive here. 133 | panic("invalid queue state") 134 | } 135 | 136 | func (q *fairFIFOQueue) ack() { 137 | q.mutex.Lock() 138 | defer q.mutex.Unlock() 139 | q.count-- 140 | } 141 | 142 | func (q *fairFIFOQueue) MarshalJSON() ([]byte, error) { 143 | q.mutex.Lock() 144 | defer q.mutex.Unlock() 145 | res := struct { 146 | Count int `json:"count"` 147 | Size int `json:"size"` 148 | Queues map[uint16]int `json:"queues"` 149 | Total uint64 `json:"packets_total"` 150 | Dropped uint64 `json:"packets_dropped"` 151 | }{ 152 | Count: q.count, 153 | Size: int(q.num) * fairFIFOQueueSize, 154 | Queues: map[uint16]int{}, 155 | Total: q.total, 156 | Dropped: q.dropped, 157 | } 158 | for h, queue := range q.queues { 159 | if c := len(queue); c > 0 { 160 | res.Queues[h] = c 161 | } 162 | } 163 | return json.Marshal(res) 164 | } 165 | -------------------------------------------------------------------------------- /router/queuefifo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "encoding/json" 19 | "sync" 20 | 21 | "github.com/matrix-org/pinecone/types" 22 | ) 23 | 24 | type fifoQueue struct { 25 | log types.Logger 26 | max int 27 | entries []chan *types.Frame 28 | mutex sync.Mutex 29 | } 30 | 31 | const fifoNoMax = 0 32 | 33 | func newFIFOQueue(max int, log types.Logger) *fifoQueue { 34 | q := &fifoQueue{ 35 | log: log, 36 | max: max, 37 | } 38 | q.reset() 39 | return q 40 | } 41 | 42 | func (q *fifoQueue) _initialise() { 43 | for i := range q.entries { 44 | q.entries[i] = nil 45 | } 46 | if q.max != 0 { 47 | // Make space for one extra entry in the capacity, since 48 | // every push appends a new channel. To prevent reallocating 49 | // the whole slice when we hit q.max to increase capacity, 50 | // make sure there's room for that trailing entry. 51 | q.entries = make([]chan *types.Frame, 1, q.max+1) 52 | q.entries[0] = make(chan *types.Frame, 1) 53 | } else { 54 | q.entries = []chan *types.Frame{ 55 | make(chan *types.Frame, 1), 56 | } 57 | } 58 | } 59 | 60 | func (q *fifoQueue) queuecount() int { // nolint:unused 61 | q.mutex.Lock() 62 | defer q.mutex.Unlock() 63 | return len(q.entries) - 1 64 | } 65 | 66 | func (q *fifoQueue) queuesize() int { // nolint:unused 67 | q.mutex.Lock() 68 | defer q.mutex.Unlock() 69 | return cap(q.entries) 70 | } 71 | 72 | func (q *fifoQueue) push(frame *types.Frame) bool { 73 | q.mutex.Lock() 74 | defer q.mutex.Unlock() 75 | if q.max != 0 && len(q.entries)-1 >= q.max { 76 | return false 77 | } 78 | ch := q.entries[len(q.entries)-1] 79 | ch <- frame 80 | close(ch) 81 | q.entries = append(q.entries, make(chan *types.Frame, 1)) 82 | return true 83 | } 84 | 85 | func (q *fifoQueue) reset() { 86 | q.mutex.Lock() 87 | defer q.mutex.Unlock() 88 | for _, ch := range q.entries { 89 | select { 90 | case frame := <-ch: 91 | if frame != nil { 92 | framePool.Put(frame) 93 | } 94 | default: 95 | } 96 | } 97 | q._initialise() 98 | } 99 | 100 | func (q *fifoQueue) pop() <-chan *types.Frame { 101 | q.mutex.Lock() 102 | defer q.mutex.Unlock() 103 | return q.entries[0] 104 | } 105 | 106 | func (q *fifoQueue) ack() { 107 | q.mutex.Lock() 108 | defer q.mutex.Unlock() 109 | q.entries = q.entries[1:] 110 | if q.max == 0 && len(q.entries) == 0 { 111 | q._initialise() 112 | } 113 | } 114 | 115 | func (q *fifoQueue) MarshalJSON() ([]byte, error) { 116 | q.mutex.Lock() 117 | defer q.mutex.Unlock() 118 | return json.Marshal(struct { 119 | Count int `json:"count"` 120 | Size int `json:"size"` 121 | }{ 122 | Count: len(q.entries) - 1, 123 | Size: cap(q.entries), 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /router/queuefifo_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/matrix-org/pinecone/types" 7 | ) 8 | 9 | func TestLimitedFIFO(t *testing.T) { 10 | q := newFIFOQueue(5, nil) 11 | 12 | // the actual allocated queue size will be 1 more than the 13 | // supplied, so that when we push an entry and assign the 14 | // next channel, that won't cause a reallocation of the queue 15 | if s := q.queuesize(); s != 6 { 16 | t.Fatalf("expected queue size to be 6 but it was %d", s) 17 | } 18 | 19 | for i := 0; i < 10; i++ { 20 | added := q.push(&types.Frame{}) 21 | switch { 22 | case i < 5 && !added: 23 | t.Fatalf("expected %d to be added", i) 24 | case i >= 5 && added: 25 | t.Fatalf("expected %d to not be added", i) 26 | } 27 | } 28 | 29 | if s := q.queuecount(); s != 5 { 30 | t.Fatalf("expected final queue count to be 5 but it was %d", s) 31 | } 32 | if s := q.queuesize(); s != 6 { 33 | t.Fatalf("expected final queue size to be 6 but it was %d", s) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /router/queuelifo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/matrix-org/pinecone/types" 21 | ) 22 | 23 | type lifoQueue struct { // nolint:unused 24 | frames []*types.Frame 25 | size int 26 | count int 27 | mutex sync.Mutex 28 | notifs chan struct{} 29 | } 30 | 31 | func newLIFOQueue(size int) *lifoQueue { // nolint:unused,deadcode 32 | q := &lifoQueue{ 33 | frames: make([]*types.Frame, size), 34 | size: size, 35 | notifs: make(chan struct{}, size), 36 | } 37 | return q 38 | } 39 | 40 | func (q *lifoQueue) queuecount() int { // nolint:unused 41 | q.mutex.Lock() 42 | defer q.mutex.Unlock() 43 | return q.count 44 | } 45 | 46 | func (q *lifoQueue) queuesize() int { // nolint:unused 47 | q.mutex.Lock() 48 | defer q.mutex.Unlock() 49 | return q.size 50 | } 51 | 52 | func (q *lifoQueue) push(frame *types.Frame) bool { // nolint:unused 53 | q.mutex.Lock() 54 | defer q.mutex.Unlock() 55 | if q.count == q.size { 56 | return false 57 | } 58 | index := q.size - q.count - 1 59 | q.frames[index] = frame 60 | q.count++ 61 | select { 62 | case q.notifs <- struct{}{}: 63 | default: 64 | panic("this should be impossible") 65 | } 66 | return true 67 | } 68 | 69 | func (q *lifoQueue) pop() (*types.Frame, bool) { // nolint:unused 70 | q.mutex.Lock() 71 | defer q.mutex.Unlock() 72 | if q.count == 0 { 73 | return nil, false 74 | } 75 | index := q.size - q.count 76 | frame := q.frames[index] 77 | q.frames[index] = nil 78 | q.count-- 79 | return frame, true 80 | } 81 | 82 | func (q *lifoQueue) ack() { // nolint:unused 83 | // no-op on this queue type 84 | } 85 | 86 | func (q *lifoQueue) reset() { // nolint:unused 87 | q.mutex.Lock() 88 | defer q.mutex.Unlock() 89 | q.count = 0 90 | for i := range q.frames { 91 | if q.frames[i] != nil { 92 | framePool.Put(q.frames[i]) 93 | q.frames[i] = nil 94 | } 95 | } 96 | close(q.notifs) 97 | for range q.notifs { 98 | } 99 | q.notifs = make(chan struct{}, q.size) 100 | } 101 | 102 | func (q *lifoQueue) wait() <-chan struct{} { // nolint:unused 103 | q.mutex.Lock() 104 | defer q.mutex.Unlock() 105 | return q.notifs 106 | } 107 | -------------------------------------------------------------------------------- /router/state_broadcast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | import ( 18 | "crypto/ed25519" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/matrix-org/pinecone/router/events" 23 | "github.com/matrix-org/pinecone/types" 24 | ) 25 | 26 | type broadcastEntry struct { 27 | Sequence types.Varu64 28 | LastSeen time.Time 29 | } 30 | 31 | // valid returns true if the broadcast hasn't expired, or false if it has. It is 32 | // required for broadcasts to time out eventually, in the case that nodes leave 33 | // the network and return later. 34 | func (e *broadcastEntry) valid() bool { 35 | return time.Since(e.LastSeen) < broadcastExpiryPeriod 36 | } 37 | 38 | // NOTE: Functions prefixed with an underscore (_) are only safe to be called 39 | // from the actor that owns them, in order to prevent data races. 40 | 41 | // _maintainBroadcasts sends out wakeup broadcasts to let local nodes know 42 | // of our presence in the network. 43 | func (s *state) _maintainBroadcasts() { 44 | select { 45 | case <-s.r.context.Done(): 46 | return 47 | default: 48 | defer s._sendBroadcastIn(wakeupBroadcastInterval) 49 | } 50 | 51 | // Clean up any broadcasts that are older than the expiry period. 52 | for k, v := range s._seenBroadcasts { 53 | if !v.valid() { 54 | delete(s._seenBroadcasts, k) 55 | } 56 | } 57 | 58 | s._sendWakeupBroadcasts() 59 | } 60 | 61 | func (s *state) _createBroadcastFrame() (*types.Frame, error) { 62 | // Construct the broadcast packet. 63 | b := frameBufferPool.Get().(*[types.MaxFrameSize]byte) 64 | defer frameBufferPool.Put(b) 65 | broadcast := types.WakeupBroadcast{ 66 | Sequence: types.Varu64(time.Now().UnixMilli()), 67 | Root: s._rootAnnouncement().Root, 68 | } 69 | if s.r.secure { 70 | protected, err := broadcast.ProtectedPayload() 71 | if err != nil { 72 | return nil, err 73 | } 74 | copy( 75 | broadcast.Signature[:], 76 | ed25519.Sign(s.r.private[:], protected), 77 | ) 78 | } 79 | n, err := broadcast.MarshalBinary(b[:]) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | // Construct the frame. 85 | send := getFrame() 86 | send.Type = types.TypeWakeupBroadcast 87 | send.SourceKey = s.r.public 88 | send.HopLimit = types.NetworkHorizonDistance 89 | send.Payload = append(send.Payload[:0], b[:n]...) 90 | 91 | return send, nil 92 | } 93 | 94 | func (s *state) _sendWakeupBroadcasts() { 95 | broadcast, err := s._createBroadcastFrame() 96 | if err != nil { 97 | s.r.log.Println("Failed creating broadcast frame:", err) 98 | } 99 | 100 | s._flood(s.r.local, broadcast, ClassicFlood) 101 | } 102 | 103 | func (s *state) _handleBroadcast(p *peer, f *types.Frame) error { 104 | // Unmarshall the broadcast 105 | var broadcast types.WakeupBroadcast 106 | if _, err := broadcast.UnmarshalBinary(f.Payload); err != nil { 107 | return fmt.Errorf("broadcast unmarshal failed: %w", err) 108 | } 109 | 110 | if s.r.secure { 111 | // Check that the broadcast message was protected by the node that claims 112 | // to have sent it. Silently drop it if there's a signature problem. 113 | protected, err := broadcast.ProtectedPayload() 114 | if err != nil { 115 | return fmt.Errorf("broadcast payload invalid: %w", err) 116 | } 117 | if !ed25519.Verify( 118 | f.SourceKey[:], 119 | protected, 120 | broadcast.Signature[:], 121 | ) { 122 | return fmt.Errorf("broadcast payload signature invalid") 123 | } 124 | } 125 | 126 | // Check that the root key in the update matches our current root. 127 | // If they don't match, silently drop the broadcast. 128 | root := s._rootAnnouncement() 129 | if root.RootPublicKey.CompareTo(broadcast.RootPublicKey) != 0 { 130 | return nil 131 | } 132 | 133 | // Check the sequence number in the broadcast. 134 | // If we have seen a higher sequence number before then there is no need 135 | // to continue forwarding it. 136 | if existing, ok := s._seenBroadcasts[f.SourceKey]; ok { 137 | sendingTooFast := time.Since(existing.LastSeen) < broadcastFilterTime 138 | repeatedSequence := broadcast.Sequence <= existing.Sequence 139 | if sendingTooFast || repeatedSequence { 140 | return nil 141 | } 142 | } 143 | s._seenBroadcasts[f.SourceKey] = broadcastEntry{ 144 | Sequence: broadcast.Sequence, 145 | LastSeen: time.Now(), 146 | } 147 | 148 | // send event to subscribers about discovered node 149 | s.r.Act(nil, func() { 150 | s.r._publish(events.BroadcastReceived{PeerID: f.SourceKey.String(), Time: uint64(time.Now().UnixNano())}) 151 | }) 152 | 153 | if f.HopLimit > 1 { 154 | f.HopLimit -= 1 155 | } else { 156 | // The packet has reached the hop limit and shouldn't be forwarded. 157 | return nil 158 | } 159 | 160 | // Forward the broadcast to all our peers except for the peer we 161 | // received it from. 162 | if f.HopLimit >= types.NetworkHorizonDistance-1 { 163 | s._flood(p, f, ClassicFlood) 164 | } else { 165 | s._flood(p, f, TreeFlood) 166 | } 167 | 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /router/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 router 16 | 17 | const ( 18 | capabilityLengthenedRootInterval = 1 << iota 19 | capabilityCryptographicSetups 20 | capabilitySetupACKs // nolint:deadcode,varcheck 21 | capabilityDedupedCoordinateInfo 22 | capabilitySoftState 23 | capabilityHybridRouting 24 | ) 25 | 26 | const ourVersion uint8 = 1 27 | const ourCapabilities uint32 = capabilityLengthenedRootInterval | capabilityCryptographicSetups | capabilityDedupedCoordinateInfo | capabilitySoftState | capabilityHybridRouting 28 | -------------------------------------------------------------------------------- /sessions/dial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 sessions 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "crypto/ed25519" 21 | "crypto/tls" 22 | "crypto/x509" 23 | "encoding/hex" 24 | "fmt" 25 | "net" 26 | 27 | "github.com/matrix-org/pinecone/types" 28 | ) 29 | 30 | // DialContext dials a given public key using the supplied network. 31 | // The network field can be used to specify which routing algorithm to 32 | // use for the connection: "ed25519+greedy" for greedy routing or "ed25519+source" 33 | // for source routing - DHT lookups and pathfinds will be performed for these 34 | // networks automatically. Otherwise, the default "ed25519" will use snake 35 | // routing. The address must be the destination public key specified in hex. 36 | // If the context expires then the connection will be torn down automatically. 37 | func (s *SessionProtocol) DialContext(ctx context.Context, network, addrstr string) (net.Conn, error) { 38 | host, _, err := net.SplitHostPort(addrstr) 39 | if err != nil { 40 | return nil, fmt.Errorf("net.SplitHostPort: %w", err) 41 | } 42 | 43 | var pk types.PublicKey 44 | var addr net.Addr 45 | pkb, err := hex.DecodeString(host) 46 | if err != nil { 47 | return nil, fmt.Errorf("hex.DecodeString: %w", err) 48 | } 49 | if len(pkb) != ed25519.PublicKeySize { 50 | return nil, fmt.Errorf("host must be length of an ed25519 public key") 51 | } 52 | copy(pk[:], pkb) 53 | addr = pk 54 | 55 | if pk == s.s.r.PublicKey() { 56 | return nil, fmt.Errorf("loopback dial") 57 | } 58 | 59 | var retrying bool 60 | retry: 61 | session, ok := s.getSession(pk) 62 | if !ok { 63 | session.Lock() 64 | tlsConfig := &tls.Config{ 65 | NextProtos: []string{s.proto}, 66 | InsecureSkipVerify: true, 67 | GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { 68 | return s.s.tlsCert, nil 69 | }, 70 | VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { 71 | if c := len(rawCerts); c != 1 { 72 | return fmt.Errorf("expected exactly one peer certificate but got %d", c) 73 | } 74 | cert, err := x509.ParseCertificate(rawCerts[0]) 75 | if err != nil { 76 | return fmt.Errorf("x509.ParseCertificate: %w", err) 77 | } 78 | public, ok := cert.PublicKey.(ed25519.PublicKey) 79 | if !ok { 80 | return fmt.Errorf("expected ed25519 public key") 81 | } 82 | if !bytes.Equal(public, pk[:]) { 83 | return fmt.Errorf("remote side returned incorrect public key") 84 | } 85 | return nil 86 | }, 87 | } 88 | 89 | session.Connection, err = s.transport.Dial(ctx, addr, tlsConfig, s.s.quicConfig) 90 | session.Unlock() 91 | if err != nil { 92 | if err == context.DeadlineExceeded { 93 | return nil, err 94 | } 95 | return nil, fmt.Errorf("quic.Dial: %w", err) 96 | } 97 | 98 | go s.sessionlistener(session) 99 | } else { 100 | session.RLock() 101 | defer session.RUnlock() 102 | } 103 | 104 | if session.Connection == nil { 105 | s.sessions.Delete(pk) 106 | return nil, fmt.Errorf("connection failed to open") 107 | } 108 | 109 | stream, err := session.OpenStreamSync(ctx) 110 | if err != nil { 111 | s.sessions.Delete(pk) 112 | if !retrying { 113 | retrying = true 114 | goto retry 115 | } 116 | return nil, fmt.Errorf("connection.OpenStream: %w", err) 117 | } 118 | 119 | return &Stream{stream, session}, nil 120 | } 121 | 122 | // Dial dials a given public key using the supplied network. 123 | // The address must be the destination public key specified in hex. 124 | func (q *SessionProtocol) Dial(network, addr string) (net.Conn, error) { 125 | return q.DialContext(context.Background(), network, addr) 126 | } 127 | 128 | // DialTLS is an alias for Dial, as all sessions are TLS-encrypted. 129 | func (q *SessionProtocol) DialTLS(network, addr string) (net.Conn, error) { 130 | return q.DialTLSContext(context.Background(), network, addr) 131 | } 132 | 133 | // DialTLSContext is an alias for DialContext, as all sessions are 134 | // TLS-encrypted. 135 | func (q *SessionProtocol) DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) { 136 | return q.DialContext(ctx, network, addr) 137 | } 138 | -------------------------------------------------------------------------------- /sessions/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 sessions 16 | 17 | import ( 18 | "net/http" 19 | "time" 20 | ) 21 | 22 | type HTTP struct { 23 | httpServer *http.Server 24 | httpMux *http.ServeMux 25 | httpTransport *http.Transport 26 | httpClient *http.Client 27 | } 28 | 29 | func (q *SessionProtocol) HTTP() *HTTP { 30 | t := &http.Transport{ 31 | DisableKeepAlives: true, 32 | MaxIdleConnsPerHost: -1, 33 | Dial: q.Dial, 34 | DialTLS: q.DialTLS, 35 | DialContext: q.DialContext, 36 | DialTLSContext: q.DialTLSContext, 37 | } 38 | 39 | h := &HTTP{ 40 | httpServer: &http.Server{ 41 | IdleTimeout: time.Second * 30, 42 | ReadTimeout: time.Second * 10, 43 | WriteTimeout: time.Second * 10, 44 | }, 45 | httpMux: &http.ServeMux{}, 46 | httpTransport: t, 47 | } 48 | 49 | h.httpServer.Handler = h.httpMux 50 | h.httpClient = &http.Client{ 51 | Transport: t, 52 | Timeout: time.Second * 30, 53 | } 54 | 55 | go h.httpServer.Serve(q) // nolint:errcheck 56 | return h 57 | } 58 | 59 | func (h *HTTP) Mux() *http.ServeMux { 60 | return h.httpMux 61 | } 62 | 63 | func (h *HTTP) Client() *http.Client { 64 | return h.httpClient 65 | } 66 | -------------------------------------------------------------------------------- /sessions/listen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 sessions 16 | 17 | import ( 18 | "bytes" 19 | "crypto/ed25519" 20 | "fmt" 21 | "net" 22 | 23 | "github.com/matrix-org/pinecone/types" 24 | ) 25 | 26 | func (q *Sessions) listener() { 27 | for { 28 | con, err := q.quicListener.Accept(q.context) 29 | if err != nil { 30 | return 31 | } 32 | 33 | key := con.RemoteAddr().(types.PublicKey) 34 | tls := con.ConnectionState().TLS 35 | if c := len(tls.PeerCertificates); c != 1 { 36 | continue 37 | } 38 | cert := tls.PeerCertificates[0] 39 | public, ok := cert.PublicKey.(ed25519.PublicKey) 40 | if !ok { 41 | continue 42 | } 43 | if !bytes.Equal(public, key[:]) { 44 | continue 45 | } 46 | 47 | if proto := q.Protocol(con.ConnectionState().TLS.NegotiatedProtocol); proto != nil { 48 | entry, ok := proto.getSession(key) 49 | entry.Lock() 50 | if ok { 51 | _ = con.CloseWithError(0, "connection replaced") 52 | } 53 | entry.Connection = con 54 | entry.Unlock() 55 | go proto.sessionlistener(entry) 56 | } 57 | } 58 | } 59 | 60 | func (s *SessionProtocol) sessionlistener(session *activeSession) { 61 | key, ok := session.RemoteAddr().(types.PublicKey) 62 | if !ok { 63 | return 64 | } 65 | 66 | defer s.sessions.Delete(key) 67 | 68 | ctx := session.Context() 69 | for { 70 | stream, err := session.AcceptStream(ctx) 71 | if err != nil { 72 | return 73 | } 74 | 75 | select { 76 | case <-ctx.Done(): 77 | case s.streams <- &Stream{stream, session}: 78 | } 79 | } 80 | } 81 | 82 | // Accept blocks until a new connection request is received. The 83 | // connection returned by this function will be TLS-encrypted. 84 | func (s *SessionProtocol) Accept() (net.Conn, error) { 85 | stream := <-s.streams 86 | if stream == nil { 87 | return nil, fmt.Errorf("listener closed") 88 | } 89 | return stream, nil 90 | } 91 | 92 | func (s *SessionProtocol) Addr() net.Addr { 93 | return s.s.r.Addr() 94 | } 95 | 96 | func (s *SessionProtocol) Close() error { 97 | var err error = nil 98 | s.closeOnce.Do(func() { 99 | close(s.streams) 100 | err = s.s.quicListener.Close() 101 | }) 102 | return err 103 | } 104 | -------------------------------------------------------------------------------- /sessions/sessions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 sessions 16 | 17 | import ( 18 | "context" 19 | "crypto/ed25519" 20 | "crypto/rand" 21 | "crypto/tls" 22 | "crypto/x509" 23 | "crypto/x509/pkix" 24 | "encoding/hex" 25 | "encoding/pem" 26 | "fmt" 27 | "math/big" 28 | "net" 29 | "sync" 30 | "time" 31 | 32 | "github.com/matrix-org/pinecone/router" 33 | "github.com/matrix-org/pinecone/types" 34 | "github.com/quic-go/quic-go" 35 | ) 36 | 37 | type Sessions struct { 38 | r *router.Router 39 | log types.Logger // logger 40 | context context.Context // router context 41 | cancel context.CancelFunc // shut down the router 42 | protocols map[string]*SessionProtocol // accepted connections by proto 43 | tlsCert *tls.Certificate // 44 | tlsServerCfg *tls.Config // 45 | quicListener quic.Listener // 46 | quicConfig *quic.Config // 47 | } 48 | 49 | type SessionProtocol struct { 50 | s *Sessions 51 | transport *quic.Transport 52 | proto string 53 | streams chan net.Conn 54 | sessions sync.Map // types.PublicKey -> *activeSession 55 | closeOnce sync.Once 56 | } 57 | 58 | type activeSession struct { 59 | quic.Connection 60 | sync.RWMutex 61 | } 62 | 63 | func NewSessions(log types.Logger, r *router.Router, protos []string) *Sessions { 64 | transport := &quic.Transport{Conn: r} 65 | ctx, cancel := context.WithCancel(context.Background()) 66 | s := &Sessions{ 67 | r: r, 68 | log: log, 69 | context: ctx, 70 | cancel: cancel, 71 | protocols: make(map[string]*SessionProtocol, len(protos)), 72 | quicConfig: &quic.Config{ 73 | MaxIdleTimeout: time.Second * 15, 74 | DisablePathMTUDiscovery: true, 75 | }, 76 | } 77 | for _, proto := range protos { 78 | s.protocols[proto] = &SessionProtocol{ 79 | s: s, 80 | transport: transport, 81 | proto: proto, 82 | streams: make(chan net.Conn, 1), 83 | } 84 | } 85 | 86 | s.tlsCert = s.generateTLSCertificate() 87 | s.tlsServerCfg = &tls.Config{ 88 | Certificates: []tls.Certificate{*s.tlsCert}, 89 | ClientAuth: tls.RequireAnyClientCert, 90 | NextProtos: protos, 91 | } 92 | 93 | listener, err := transport.Listen(s.tlsServerCfg, s.quicConfig) 94 | if err != nil { 95 | panic(fmt.Errorf("quic.NewSocketFromPacketConnNoClose: %w", err)) 96 | } 97 | s.quicListener = *listener 98 | 99 | go s.listener() 100 | return s 101 | } 102 | 103 | func (s *Sessions) Close() error { 104 | s.cancel() 105 | return nil 106 | } 107 | 108 | func (s *Sessions) Protocol(proto string) *SessionProtocol { 109 | return s.protocols[proto] 110 | } 111 | 112 | func (s *SessionProtocol) Sessions() []ed25519.PublicKey { 113 | var sessions []ed25519.PublicKey 114 | s.sessions.Range(func(k, _ interface{}) bool { 115 | switch pk := k.(type) { 116 | case types.PublicKey: 117 | sessions = append(sessions, pk[:]) 118 | default: 119 | } 120 | return true 121 | }) 122 | return sessions 123 | } 124 | 125 | func (p *SessionProtocol) getSession(pk types.PublicKey) (*activeSession, bool) { 126 | v, ok := p.sessions.LoadOrStore(pk, &activeSession{}) 127 | return v.(*activeSession), ok 128 | } 129 | 130 | func (s *Sessions) generateTLSCertificate() *tls.Certificate { 131 | private, public := s.r.PrivateKey(), s.r.PublicKey() 132 | id := hex.EncodeToString(public[:]) 133 | 134 | template := x509.Certificate{ 135 | Subject: pkix.Name{ 136 | CommonName: id, 137 | }, 138 | SerialNumber: big.NewInt(1), 139 | NotAfter: time.Now().Add(time.Hour * 24 * 365), 140 | DNSNames: []string{id}, 141 | } 142 | 143 | certDER, err := x509.CreateCertificate( 144 | rand.Reader, 145 | &template, 146 | &template, 147 | ed25519.PublicKey(public[:]), 148 | ed25519.PrivateKey(private[:]), 149 | ) 150 | if err != nil { 151 | panic(fmt.Errorf("x509.CreateCertificate: %w", err)) 152 | } 153 | privateKey, err := x509.MarshalPKCS8PrivateKey(ed25519.PrivateKey(private[:])) 154 | if err != nil { 155 | panic(fmt.Errorf("x509.MarshalPKCS8PrivateKey: %w", err)) 156 | } 157 | 158 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 159 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKey}) 160 | 161 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 162 | if err != nil { 163 | panic(fmt.Errorf("tls.X509KeyPair: %w", err)) 164 | } 165 | 166 | return &tlsCert 167 | } 168 | -------------------------------------------------------------------------------- /sessions/streams.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 sessions 16 | 17 | import ( 18 | "net" 19 | 20 | "github.com/quic-go/quic-go" 21 | ) 22 | 23 | type Stream struct { 24 | quic.Stream 25 | connection quic.Connection 26 | } 27 | 28 | func (s *Stream) LocalAddr() net.Addr { 29 | return s.connection.LocalAddr() 30 | } 31 | 32 | func (s *Stream) RemoteAddr() net.Addr { 33 | return s.connection.RemoteAddr() 34 | } 35 | -------------------------------------------------------------------------------- /types/announcement_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "bytes" 19 | "crypto/ed25519" 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | func TestMarshalUnmarshalAnnouncement(t *testing.T) { 25 | pkr, _, _ := ed25519.GenerateKey(nil) 26 | pk1, sk1, _ := ed25519.GenerateKey(nil) 27 | pk2, sk2, _ := ed25519.GenerateKey(nil) 28 | pk3, sk3, _ := ed25519.GenerateKey(nil) 29 | input := &SwitchAnnouncement{ 30 | Root: Root{ 31 | RootSequence: 1, 32 | }, 33 | } 34 | copy(input.RootPublicKey[:], pkr) 35 | var err error 36 | err = input.Sign(sk1, 1) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | err = input.Sign(sk2, 2) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | err = input.Sign(sk3, 3) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | var buffer [65535]byte 49 | n, err := input.MarshalBinary(buffer[:]) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | var output SwitchAnnouncement 54 | if _, err = output.UnmarshalBinary(buffer[:n]); err != nil { 55 | t.Fatal(err) 56 | } 57 | if !bytes.Equal(pkr, output.RootPublicKey[:]) { 58 | fmt.Println("expected:", pkr) 59 | fmt.Println("got:", output.RootPublicKey) 60 | t.Fatalf("first public key doesn't match") 61 | } 62 | if len(output.Signatures) < 3 { 63 | t.Fatalf("not enough signatures were found (should be 3)") 64 | } 65 | if !bytes.Equal(pk1, output.Signatures[0].PublicKey[:]) { 66 | fmt.Println("expected:", pk1) 67 | fmt.Println("got:", output.Signatures[0].PublicKey) 68 | t.Fatalf("first public key doesn't match") 69 | } 70 | if !bytes.Equal(pk2, output.Signatures[1].PublicKey[:]) { 71 | fmt.Println("expected:", pk2) 72 | fmt.Println("got:", output.Signatures[1].PublicKey) 73 | t.Fatalf("second public key doesn't match") 74 | } 75 | if !bytes.Equal(pk3, output.Signatures[2].PublicKey[:]) { 76 | fmt.Println("expected:", pk3) 77 | fmt.Println("got:", output.Signatures[2].PublicKey) 78 | t.Fatalf("third public key doesn't match") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /types/broadcast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "crypto/ed25519" 19 | "fmt" 20 | ) 21 | 22 | type WakeupBroadcast struct { 23 | Sequence Varu64 24 | Root 25 | Signature [ed25519.SignatureSize]byte 26 | } 27 | 28 | func (w *WakeupBroadcast) ProtectedPayload() ([]byte, error) { 29 | buffer := make([]byte, w.Sequence.Length()+w.Root.Length()) 30 | offset := 0 31 | n, err := w.Sequence.MarshalBinary(buffer[:]) 32 | if err != nil { 33 | return nil, fmt.Errorf("w.Sequence.MarshalBinary: %w", err) 34 | } 35 | offset += n 36 | offset += copy(buffer[offset:], w.RootPublicKey[:]) 37 | n, err = w.RootSequence.MarshalBinary(buffer[offset:]) 38 | if err != nil { 39 | return nil, fmt.Errorf("w.RootSequence.MarshalBinary: %w", err) 40 | } 41 | offset += n 42 | return buffer[:offset], nil 43 | } 44 | 45 | func (w *WakeupBroadcast) MarshalBinary(buf []byte) (int, error) { 46 | if len(buf) < w.Sequence.Length()+w.Root.Length()+ed25519.SignatureSize { 47 | return 0, fmt.Errorf("buffer too small") 48 | } 49 | offset := 0 50 | n, err := w.Sequence.MarshalBinary(buf[offset:]) 51 | if err != nil { 52 | return 0, fmt.Errorf("w.Sequence.MarshalBinary: %w", err) 53 | } 54 | offset += n 55 | offset += copy(buf[offset:], w.RootPublicKey[:]) 56 | n, err = w.RootSequence.MarshalBinary(buf[offset:]) 57 | if err != nil { 58 | return 0, fmt.Errorf("w.RootSequence.MarshalBinary: %w", err) 59 | } 60 | offset += n 61 | offset += copy(buf[offset:], w.Signature[:]) 62 | return offset, nil 63 | } 64 | 65 | func (w *WakeupBroadcast) UnmarshalBinary(buf []byte) (int, error) { 66 | if len(buf) < w.Sequence.MinLength()+w.Root.MinLength()+ed25519.SignatureSize { 67 | return 0, fmt.Errorf("buffer too small") 68 | } 69 | offset := 0 70 | n, err := w.Sequence.UnmarshalBinary(buf[offset:]) 71 | if err != nil { 72 | return 0, fmt.Errorf("w.Sequence.UnmarshalBinary: %w", err) 73 | } 74 | offset += n 75 | offset += copy(w.RootPublicKey[:], buf[offset:]) 76 | n, err = w.RootSequence.UnmarshalBinary(buf[offset:]) 77 | if err != nil { 78 | return 0, fmt.Errorf("w.RootSequence.UnmarshalBinary: %w", err) 79 | } 80 | offset += n 81 | offset += copy(w.Signature[:], buf[offset:]) 82 | return offset, nil 83 | } 84 | -------------------------------------------------------------------------------- /types/coordinates.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "strings" 21 | ) 22 | 23 | type SwitchPortID Varu64 24 | type Coordinates []SwitchPortID 25 | 26 | func (s Coordinates) Network() string { 27 | return "tree" 28 | } 29 | 30 | func (s Coordinates) Len() int { 31 | return len(s) 32 | } 33 | 34 | func (s Coordinates) Swap(i, j int) { 35 | s[i], s[j] = s[j], s[i] 36 | } 37 | 38 | func (s Coordinates) Less(i, j int) bool { 39 | return s[i] < s[j] 40 | } 41 | 42 | func (s Coordinates) String() string { 43 | ports := make([]string, 0, len(s)) 44 | for _, p := range s { 45 | ports = append(ports, fmt.Sprintf("%d", p)) 46 | } 47 | return "[" + strings.Join(ports, " ") + "]" 48 | } 49 | 50 | func (p Coordinates) MarshalBinary(buf []byte) (int, error) { 51 | l := 2 52 | for _, a := range p { 53 | n, err := Varu64(a).MarshalBinary(buf[l:]) 54 | if err != nil { 55 | return 0, fmt.Errorf("Varu64(a).MarshalBinary: %w", err) 56 | } 57 | l += n 58 | } 59 | binary.BigEndian.PutUint16(buf[:2], uint16(l-2)) 60 | return l, nil 61 | } 62 | 63 | func (p *Coordinates) UnmarshalBinary(b []byte) (int, error) { 64 | l := int(binary.BigEndian.Uint16(b[:2])) 65 | if l == 0 { 66 | return 2, nil 67 | } 68 | if rl := len(b); rl < 2+l { 69 | return 0, fmt.Errorf("expecting %d bytes but got %d bytes", 2+l, rl) 70 | } 71 | ports := make(Coordinates, 0, l) 72 | read := 2 73 | b = b[read : l+2] 74 | for { 75 | if len(b) < 1 { 76 | break 77 | } 78 | var id Varu64 79 | l, err := id.UnmarshalBinary(b) 80 | if err != nil { 81 | return 0, fmt.Errorf("id.UnmarshalBinary: %w", err) 82 | } 83 | ports = append(ports, SwitchPortID(id)) 84 | b = b[l:] 85 | read += l 86 | } 87 | *p = ports 88 | return read, nil 89 | } 90 | 91 | func (p Coordinates) MarshalJSON() ([]byte, error) { 92 | s := make([]string, 0, len(p)) 93 | for _, id := range p { 94 | s = append(s, fmt.Sprintf("%d", id)) 95 | } 96 | return []byte(`"[` + strings.Join(s, " ") + `]"`), nil 97 | } 98 | 99 | func (p Coordinates) EqualTo(o Coordinates) bool { 100 | if len(p) != len(o) { 101 | return false 102 | } 103 | for i := range p { 104 | if p[i] != o[i] { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | 111 | func (a *Coordinates) Copy() Coordinates { 112 | return append(Coordinates{}, *a...) 113 | } 114 | 115 | func (a Coordinates) DistanceTo(b Coordinates) int { 116 | ancestor := getCommonPrefix(a, b) 117 | return len(a) + len(b) - 2*ancestor 118 | } 119 | 120 | func getCommonPrefix(a, b Coordinates) int { 121 | c := 0 122 | l := len(a) 123 | if len(b) < l { 124 | l = len(b) 125 | } 126 | for i := 0; i < l; i++ { 127 | if a[i] != b[i] { 128 | break 129 | } 130 | c++ 131 | } 132 | return c 133 | } 134 | -------------------------------------------------------------------------------- /types/coordinates_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | func TestSwitchPorts(t *testing.T) { 23 | var b [7]byte 24 | expected := []byte{0, 5, 1, 2, 3, 159, 32} 25 | input := Coordinates{1, 2, 3, 4000} 26 | _, err := input.MarshalBinary(b[:]) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | if !bytes.Equal(b[:], expected) { 31 | t.Fatalf("MarshalBinary produced %v, expected %v", b, expected) 32 | } 33 | var output Coordinates 34 | if _, err := output.UnmarshalBinary(b[:]); err != nil { 35 | t.Fatal(err) 36 | } 37 | if !input.EqualTo(output) { 38 | t.Fatalf("Expected %v, got %v", input, output) 39 | } 40 | } 41 | 42 | func TestSwitchPortDistances(t *testing.T) { 43 | root := Coordinates{} 44 | parent := Coordinates{1, 2, 3} 45 | us := Coordinates{1, 2, 3, 4} 46 | other := Coordinates{1, 3, 3, 4, 7, 6, 1} 47 | if dist := us.DistanceTo(root); dist != 4 { 48 | t.Fatalf("distance from us to root should be 4, got %d", dist) 49 | } 50 | if dist := parent.DistanceTo(root); dist != 3 { 51 | t.Fatalf("distance from parent to root should be 3, got %d", dist) 52 | } 53 | if dist := root.DistanceTo(us); dist != 4 { 54 | t.Fatalf("distance from root to us should be 4, got %d", dist) 55 | } 56 | if dist := root.DistanceTo(parent); dist != 3 { 57 | t.Fatalf("distance from root to parent should be 3, got %d", dist) 58 | } 59 | if dist := us.DistanceTo(other); dist != 9 { 60 | t.Fatalf("distance from us to other should be 9, got %d", dist) 61 | } 62 | if dist := parent.DistanceTo(other); dist != 8 { 63 | t.Fatalf("distance from parent to other should be 8, got %d", dist) 64 | } 65 | if dist := root.DistanceTo(other); dist != 7 { 66 | t.Fatalf("distance from root to other should be 7, got %d", dist) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /types/ed25519.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "bytes" 19 | "crypto/ed25519" 20 | "encoding/hex" 21 | "fmt" 22 | ) 23 | 24 | type PublicKey [ed25519.PublicKeySize]byte 25 | type PrivateKey [ed25519.PrivateKeySize]byte 26 | type Signature [ed25519.SignatureSize]byte 27 | 28 | func (a PrivateKey) Public() PublicKey { 29 | var public PublicKey 30 | ed := make(ed25519.PrivateKey, ed25519.PrivateKeySize) 31 | copy(ed, a[:]) 32 | copy(public[:], ed.Public().(ed25519.PublicKey)) 33 | return public 34 | } 35 | 36 | var FullMask = PublicKey{ 37 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 38 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 40 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 41 | } 42 | 43 | func (a PublicKey) IsEmpty() bool { 44 | empty := PublicKey{} 45 | return a == empty 46 | } 47 | 48 | func (a PublicKey) EqualMaskTo(b, m PublicKey) bool { 49 | for i := range a { 50 | if (a[i] & m[i]) != (b[i] & m[i]) { 51 | return false 52 | } 53 | } 54 | return true 55 | } 56 | 57 | func (a PublicKey) CompareTo(b PublicKey) int { 58 | return bytes.Compare(a[:], b[:]) 59 | } 60 | 61 | func (a PublicKey) String() string { 62 | return fmt.Sprintf("%v", hex.EncodeToString(a[:])) 63 | } 64 | 65 | func (a PublicKey) MarshalJSON() ([]byte, error) { 66 | return []byte(`"` + a.String() + `"`), nil 67 | } 68 | 69 | func (a PublicKey) Network() string { 70 | return "ed25519" 71 | } 72 | -------------------------------------------------------------------------------- /types/ed25519_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "testing" 4 | 5 | func TestPartialKeyMatch(t *testing.T) { 6 | a := PublicKey{1, 2, 3, 3, 3} 7 | b := PublicKey{1, 2, 4, 4, 4} 8 | 9 | if !a.EqualMaskTo(b, PublicKey{0xFF, 0xFF, 0, 0, 0}) { 10 | t.Fatalf("Should have matched but didn't") 11 | } 12 | if a.EqualMaskTo(b, PublicKey{0xFF, 0xFF, 0xFF, 0, 0}) { 13 | t.Fatalf("Should not have matched but did") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /types/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | type Logger interface { 18 | Println(...interface{}) 19 | Printf(string, ...interface{}) 20 | } 21 | -------------------------------------------------------------------------------- /types/signaturehop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "crypto/ed25519" 19 | "fmt" 20 | ) 21 | 22 | type SignatureWithHop struct { 23 | Hop Varu64 24 | PublicKey PublicKey 25 | Signature Signature 26 | } 27 | 28 | const SignatureWithHopMinSize = ed25519.PublicKeySize + ed25519.SignatureSize + 1 29 | 30 | func (a *SignatureWithHop) UnmarshalBinary(data []byte) (int, error) { 31 | if size := len(data); size < SignatureWithHopMinSize { 32 | return 0, fmt.Errorf("SignatureWithHop expects at least %d bytes, got %d bytes", SignatureWithHopMinSize, size) 33 | } 34 | l, err := a.Hop.UnmarshalBinary(data) 35 | if err != nil { 36 | return 0, fmt.Errorf("a.Hop.UnmarshalBinary: %w", err) 37 | } 38 | offset := l 39 | remaining := data[offset:] 40 | remaining = remaining[copy(a.PublicKey[:], remaining):] 41 | remaining = remaining[copy(a.Signature[:], remaining):] 42 | return len(data) - len(remaining), nil 43 | } 44 | 45 | func (a *SignatureWithHop) MarshalBinary(data []byte) (int, error) { 46 | offset, err := a.Hop.MarshalBinary(data[:]) 47 | if err != nil { 48 | return 0, fmt.Errorf("a.Hop.MarshalBinary: %w", err) 49 | } 50 | if len(data) < SignatureWithHopMinSize { 51 | return 0, fmt.Errorf("buffer is not big enough (must be %d bytes)", SignatureWithHopMinSize) 52 | } 53 | offset += copy(data[offset:offset+ed25519.PublicKeySize], a.PublicKey[:]) 54 | offset += copy(data[offset:offset+ed25519.SignatureSize], a.Signature[:]) 55 | return offset, nil 56 | } 57 | -------------------------------------------------------------------------------- /types/varu64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import "fmt" 18 | 19 | type Varu64 uint64 20 | 21 | func (n Varu64) MarshalBinary(b []byte) (int, error) { 22 | if len(b) < n.Length() { 23 | return 0, fmt.Errorf("input slice too small") 24 | } 25 | l := n.Length() 26 | i := l - 1 27 | b[i] = byte(n & 0x7f) 28 | for n >>= 7; n != 0; n >>= 7 { 29 | i-- 30 | b[i] = byte(n | 0x80) 31 | } 32 | return l, nil 33 | } 34 | 35 | func (n *Varu64) UnmarshalBinary(buf []byte) (int, error) { 36 | l := 0 37 | *n = Varu64(0) 38 | for _, b := range buf { 39 | *n <<= 7 40 | *n |= Varu64(b & 0x7f) 41 | l++ 42 | if b&0x80 == 0 { 43 | break 44 | } 45 | } 46 | return l, nil 47 | } 48 | 49 | func (n Varu64) Length() int { 50 | l := 1 51 | for e := n >> 7; e > 0; e >>= 7 { 52 | l++ 53 | } 54 | return l 55 | } 56 | 57 | func (n Varu64) MinLength() int { 58 | return 1 59 | } 60 | -------------------------------------------------------------------------------- /types/varu64_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | func TestMarshalBinaryVaru64(t *testing.T) { 23 | var bin [4]byte 24 | for input, expected := range map[Varu64][]byte{ 25 | 0: {0}, 26 | 1: {1}, 27 | 12: {12}, 28 | 123: {123}, 29 | 1234: {137, 82}, 30 | 12345: {224, 57}, 31 | 123456: {135, 196, 64}, 32 | 1234567: {203, 173, 7}, 33 | 12345678: {133, 241, 194, 78}, 34 | } { 35 | n, err := input.MarshalBinary(bin[:]) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if !bytes.Equal(bin[:n], expected) { 40 | t.Fatalf("for %d expected %v, got %v", input, expected, bin[:n]) 41 | } 42 | if length := input.Length(); length != len(expected) { 43 | t.Fatalf("for %d expected length %d, got %d", input, length, len(expected)) 44 | } 45 | } 46 | } 47 | 48 | func TestUnmarshalBinaryVaru64(t *testing.T) { 49 | for input, expected := range map[[7]byte]Varu64{ 50 | {0}: 0, 51 | {1}: 1, 52 | {12}: 12, 53 | {123}: 123, 54 | {137, 82}: 1234, 55 | {224, 57}: 12345, 56 | {135, 196, 64}: 123456, 57 | {203, 173, 7}: 1234567, 58 | {133, 241, 194, 78}: 12345678, 59 | {133, 241, 194, 78, 1, 2, 3}: 12345678, 60 | } { 61 | var num Varu64 62 | if _, err := num.UnmarshalBinary(input[:]); err != nil { 63 | t.Fatal(err) 64 | } 65 | if num != expected { 66 | t.Fatalf("expected %v, got %v", expected, num) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /types/virtualsnake.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "fmt" 6 | ) 7 | 8 | type VirtualSnakeBootstrap struct { 9 | Sequence Varu64 10 | Root 11 | Signature [ed25519.SignatureSize]byte 12 | } 13 | 14 | type VirtualSnakeWatermark struct { 15 | PublicKey PublicKey `json:"public_key"` 16 | Sequence Varu64 `json:"sequence"` 17 | } 18 | 19 | func (a VirtualSnakeWatermark) WorseThan(b VirtualSnakeWatermark) bool { 20 | diff := a.PublicKey.CompareTo(b.PublicKey) 21 | return diff > 0 || (diff == 0 && a.Sequence != 0 && a.Sequence < b.Sequence) 22 | } 23 | 24 | func (v *VirtualSnakeBootstrap) ProtectedPayload() ([]byte, error) { 25 | buffer := make([]byte, v.Sequence.Length()+v.Root.Length()) 26 | offset := 0 27 | n, err := v.Sequence.MarshalBinary(buffer[:]) 28 | if err != nil { 29 | return nil, fmt.Errorf("v.Sequence.MarshalBinary: %w", err) 30 | } 31 | offset += n 32 | offset += copy(buffer[offset:], v.RootPublicKey[:]) 33 | n, err = v.RootSequence.MarshalBinary(buffer[offset:]) 34 | if err != nil { 35 | return nil, fmt.Errorf("v.RootSequence.MarshalBinary: %w", err) 36 | } 37 | offset += n 38 | return buffer[:offset], nil 39 | } 40 | 41 | func (v *VirtualSnakeBootstrap) MarshalBinary(buf []byte) (int, error) { 42 | if len(buf) < v.Sequence.Length()+v.Root.Length()+ed25519.SignatureSize { 43 | return 0, fmt.Errorf("buffer too small") 44 | } 45 | offset := 0 46 | n, err := v.Sequence.MarshalBinary(buf[offset:]) 47 | if err != nil { 48 | return 0, fmt.Errorf("v.Sequence.MarshalBinary: %w", err) 49 | } 50 | offset += n 51 | offset += copy(buf[offset:], v.RootPublicKey[:]) 52 | n, err = v.RootSequence.MarshalBinary(buf[offset:]) 53 | if err != nil { 54 | return 0, fmt.Errorf("v.RootSequence.MarshalBinary: %w", err) 55 | } 56 | offset += n 57 | offset += copy(buf[offset:], v.Signature[:]) 58 | return offset, nil 59 | } 60 | 61 | func (v *VirtualSnakeBootstrap) UnmarshalBinary(buf []byte) (int, error) { 62 | if len(buf) < v.Sequence.MinLength()+v.Root.MinLength()+ed25519.SignatureSize { 63 | return 0, fmt.Errorf("buffer too small") 64 | } 65 | offset := 0 66 | n, err := v.Sequence.UnmarshalBinary(buf[offset:]) 67 | if err != nil { 68 | return 0, fmt.Errorf("v.Sequence.UnmarshalBinary: %w", err) 69 | } 70 | offset += n 71 | offset += copy(v.RootPublicKey[:], buf[offset:]) 72 | n, err = v.RootSequence.UnmarshalBinary(buf[offset:]) 73 | if err != nil { 74 | return 0, fmt.Errorf("v.RootSequence.UnmarshalBinary: %w", err) 75 | } 76 | offset += n 77 | offset += copy(v.Signature[:], buf[offset:]) 78 | return offset, nil 79 | } 80 | -------------------------------------------------------------------------------- /types/virtualsnake_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. 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 types 16 | 17 | import ( 18 | "bytes" 19 | "crypto/ed25519" 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | func TestMarshalUnmarshalBootstrap(t *testing.T) { 25 | pkr, _, _ := ed25519.GenerateKey(nil) 26 | _, sk1, _ := ed25519.GenerateKey(nil) 27 | input := &VirtualSnakeBootstrap{ 28 | Sequence: 7, 29 | Root: Root{ 30 | RootSequence: 1, 31 | }, 32 | } 33 | copy(input.RootPublicKey[:], pkr) 34 | var err error 35 | protected, err := input.ProtectedPayload() 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | copy( 40 | input.Signature[:], 41 | ed25519.Sign(sk1[:], protected), 42 | ) 43 | var buffer [65535]byte 44 | n, err := input.MarshalBinary(buffer[:]) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | var output VirtualSnakeBootstrap 50 | if _, err = output.UnmarshalBinary(buffer[:n]); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | if output.Sequence != input.Sequence { 55 | fmt.Println("expected:", input.Sequence) 56 | fmt.Println("got:", output.Sequence) 57 | t.Fatalf("bootstrap sequence doesn't match") 58 | } 59 | if !bytes.Equal(pkr, output.RootPublicKey[:]) { 60 | fmt.Println("expected:", pkr) 61 | fmt.Println("got:", output.RootPublicKey) 62 | t.Fatalf("root public key doesn't match") 63 | } 64 | if output.RootSequence != input.RootSequence { 65 | fmt.Println("expected:", input.RootSequence) 66 | fmt.Println("got:", output.RootSequence) 67 | t.Fatalf("root sequence doesn't match") 68 | } 69 | if !bytes.Equal(input.Signature[:], output.Signature[:]) { 70 | fmt.Println("expected:", input.Signature) 71 | fmt.Println("got:", output.Signature) 72 | t.Fatalf("root public key doesn't match") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /util/distance.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 util 16 | 17 | import ( 18 | "crypto/ed25519" 19 | 20 | "github.com/matrix-org/pinecone/types" 21 | ) 22 | 23 | func LessThan(first, second types.PublicKey) bool { 24 | for i := 0; i < ed25519.PublicKeySize; i++ { 25 | if first[i] < second[i] { 26 | return true 27 | } 28 | if first[i] > second[i] { 29 | return false 30 | } 31 | } 32 | return false 33 | } 34 | 35 | // DHTOrdered returns true if the order of A, B and C is 36 | // correct, where A < B < C without wrapping. 37 | func DHTOrdered(a, b, c types.PublicKey) bool { 38 | return LessThan(a, b) && LessThan(b, c) 39 | } 40 | 41 | // DHTWrappedOrdered returns true if the ordering of A, B 42 | // and C is correct, where we may wrap around from C to A. 43 | // This gives us the property of the successor always being 44 | // a+1 and the predecessor being a+sizeofkeyspace. 45 | func DHTWrappedOrdered(a, b, c types.PublicKey) bool { 46 | ab, bc, ca := LessThan(a, b), LessThan(b, c), LessThan(c, a) 47 | switch { 48 | case ab && bc: 49 | return true 50 | case bc && ca: 51 | return true 52 | case ca && ab: 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | func ReverseOrdering(target types.PublicKey, input []types.PublicKey) func(i, j int) bool { 59 | return func(i, j int) bool { 60 | return DHTWrappedOrdered(input[i], target, input[j]) 61 | } 62 | } 63 | 64 | func ForwardOrdering(target types.PublicKey, input []types.PublicKey) func(i, j int) bool { 65 | return func(i, j int) bool { 66 | return DHTWrappedOrdered(target, input[i], input[j]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /util/distance_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 util 16 | 17 | import ( 18 | "fmt" 19 | "sort" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/matrix-org/pinecone/types" 24 | ) 25 | 26 | func TestDHTWrappedOrdering(t *testing.T) { 27 | target := types.PublicKey{5} 28 | input := []types.PublicKey{ 29 | {1}, {2}, {3}, {9}, {7}, {4}, {6}, {8}, {0}, 30 | } 31 | reverse := []types.PublicKey{ 32 | {4}, {3}, {2}, {1}, {0}, {9}, {8}, {7}, {6}, 33 | } 34 | forward := []types.PublicKey{ 35 | {6}, {7}, {8}, {9}, {0}, {1}, {2}, {3}, {4}, 36 | } 37 | 38 | out := func(show []types.PublicKey) string { 39 | var parts []string 40 | for _, i := range show { 41 | parts = append(parts, fmt.Sprintf("%d", i[0])) 42 | } 43 | return strings.Join(parts, ", ") 44 | } 45 | 46 | sort.SliceStable(input, ForwardOrdering(target, input)) 47 | for i := range input { 48 | if input[i] != forward[i] { 49 | t.Log("Want:", out(forward)) 50 | t.Log("Got: ", out(input)) 51 | t.Fatalf("Successor ordering incorrect") 52 | } 53 | } 54 | 55 | sort.SliceStable(input, ReverseOrdering(target, input)) 56 | for i := range input { 57 | if input[i] != reverse[i] { 58 | t.Log("Want:", out(reverse)) 59 | t.Log("Got: ", out(input)) 60 | t.Fatalf("Predecessor ordering incorrect") 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /util/overlay.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 util 16 | 17 | import ( 18 | "sort" 19 | 20 | "github.com/matrix-org/pinecone/types" 21 | ) 22 | 23 | type Overlay struct { 24 | target types.PublicKey 25 | ourkey types.PublicKey 26 | keys []types.PublicKey 27 | } 28 | 29 | func (o *Overlay) candidates() ([]types.PublicKey, error) { 30 | sort.SliceStable(o.keys, ForwardOrdering(o.ourkey, o.keys)) 31 | 32 | mustWrap := o.target.CompareTo(o.ourkey) < 0 33 | hasWrapped := !mustWrap 34 | 35 | cap, last := len(o.keys), o.keys[0] 36 | for i, k := range o.keys { 37 | if hasWrapped { 38 | if k.CompareTo(o.target) > 0 { 39 | cap = i 40 | break 41 | } 42 | } else { 43 | hasWrapped = k.CompareTo(last) < 0 44 | } 45 | } 46 | o.keys = o.keys[:cap] 47 | 48 | nc := len(o.keys) 49 | if nc > 3 { 50 | nc = 3 51 | } 52 | 53 | candidates := []types.PublicKey{} 54 | candidates = append(candidates, o.keys[:nc]...) 55 | 56 | kc := len(o.keys) 57 | if kc > 10 { 58 | candidates = append(candidates, o.keys[kc/8]) 59 | } 60 | if kc > 7 { 61 | candidates = append(candidates, o.keys[kc/4]) 62 | } 63 | if kc > 4 { 64 | candidates = append(candidates, o.keys[kc/2]) 65 | } 66 | 67 | return candidates, nil 68 | } 69 | -------------------------------------------------------------------------------- /util/overlay_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 util 16 | 17 | import ( 18 | "crypto/ed25519" 19 | "fmt" 20 | "testing" 21 | 22 | "github.com/matrix-org/pinecone/types" 23 | ) 24 | 25 | func TestOverlaySorting(t *testing.T) { 26 | overlay := &Overlay{} 27 | opk, _, _ := ed25519.GenerateKey(nil) 28 | tpk, _, _ := ed25519.GenerateKey(nil) 29 | copy(overlay.ourkey[:], opk) 30 | copy(overlay.target[:], tpk) 31 | 32 | fmt.Println("Our key: ", overlay.ourkey) 33 | fmt.Println("Target key:", overlay.target) 34 | 35 | for i := 0; i < 32; i++ { 36 | pk, _, _ := ed25519.GenerateKey(nil) 37 | k := types.PublicKey{} 38 | copy(k[:], pk) 39 | overlay.keys = append(overlay.keys[:], k) 40 | } 41 | 42 | fmt.Println("Candidates:") 43 | candidates, err := overlay.candidates() 44 | if err != nil { 45 | panic(err) 46 | } 47 | for _, k := range candidates { 48 | fmt.Println("*", k) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /util/slowconn.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "time" 7 | ) 8 | 9 | type SlowConn struct { 10 | net.Conn 11 | ReadDelay time.Duration 12 | ReadJitter time.Duration 13 | WriteDelay time.Duration 14 | WriteJitter time.Duration 15 | } 16 | 17 | func (p *SlowConn) Read(b []byte) (n int, err error) { 18 | duration := p.ReadDelay 19 | if j := p.ReadJitter; j > 0 { 20 | duration += time.Duration(rand.Intn(int(j))) 21 | } 22 | if duration > 0 { 23 | time.Sleep(duration) 24 | } 25 | return p.Conn.Read(b) 26 | } 27 | 28 | func (p *SlowConn) Write(b []byte) (n int, err error) { 29 | duration := p.WriteDelay 30 | if j := p.WriteJitter; j > 0 { 31 | duration += time.Duration(rand.Intn(int(j))) 32 | } 33 | if duration > 0 { 34 | time.Sleep(duration) 35 | } 36 | return p.Conn.Write(b) 37 | } 38 | -------------------------------------------------------------------------------- /util/websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Matrix.org Foundation C.I.C. 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 util 16 | 17 | import ( 18 | "io" 19 | "net" 20 | "time" 21 | 22 | "github.com/gorilla/websocket" 23 | ) 24 | 25 | func WrapWebSocketConn(c *websocket.Conn) *WebSocketConn { 26 | return &WebSocketConn{c: c} 27 | } 28 | 29 | type WebSocketConn struct { 30 | r io.Reader 31 | c *websocket.Conn 32 | } 33 | 34 | func (c *WebSocketConn) Write(p []byte) (int, error) { 35 | err := c.c.WriteMessage(websocket.BinaryMessage, p) 36 | if err != nil { 37 | return 0, err 38 | } 39 | return len(p), nil 40 | } 41 | 42 | func (c *WebSocketConn) Read(p []byte) (int, error) { 43 | for { 44 | if c.r == nil { 45 | // Advance to next message. 46 | var err error 47 | _, c.r, err = c.c.NextReader() 48 | if err != nil { 49 | return 0, err 50 | } 51 | } 52 | n, err := c.r.Read(p) 53 | if err == io.EOF { 54 | // At end of message. 55 | c.r = nil 56 | if n > 0 { 57 | return n, nil 58 | } else { 59 | // No data read, continue to next message. 60 | continue 61 | } 62 | } 63 | return n, err 64 | } 65 | } 66 | 67 | func (c *WebSocketConn) Close() error { 68 | return c.c.Close() 69 | } 70 | 71 | func (c *WebSocketConn) LocalAddr() net.Addr { 72 | return c.c.LocalAddr() 73 | } 74 | 75 | func (c *WebSocketConn) RemoteAddr() net.Addr { 76 | return c.c.RemoteAddr() 77 | } 78 | 79 | func (c *WebSocketConn) SetDeadline(t time.Time) error { 80 | if err := c.SetReadDeadline(t); err != nil { 81 | return err 82 | } 83 | if err := c.SetWriteDeadline(t); err != nil { 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | func (c *WebSocketConn) SetReadDeadline(t time.Time) error { 90 | return c.c.SetReadDeadline(t) 91 | } 92 | 93 | func (c *WebSocketConn) SetWriteDeadline(t time.Time) error { 94 | return c.c.SetWriteDeadline(t) 95 | } 96 | --------------------------------------------------------------------------------