├── .github └── workflows │ ├── ci.yml │ └── pkg.yml ├── .gitignore ├── .golangci.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── LICENSE.rtf ├── README.md ├── SECURITY.md ├── appveyor.yml ├── babel.config.json ├── build ├── clean ├── cmd ├── genkeys │ └── main.go ├── mesh │ └── main.go └── meshctl │ ├── cmd_line_env.go │ └── main.go ├── contrib ├── mobile │ ├── build │ ├── mobile.go │ ├── mobile_android.go │ ├── mobile_ios.go │ ├── mobile_other.go │ └── mobile_test.go ├── msi │ └── msversion.sh └── semver │ ├── name.sh │ └── version.sh ├── go.mod ├── go.sum ├── misc ├── run-schannel-netns └── run-twolink-test └── src ├── config ├── config.go └── config_test.go ├── core ├── address.go ├── address_test.go ├── api.go ├── core.go ├── core_test.go ├── debug.go ├── link.go ├── link_linux.go ├── link_mpath.go ├── link_mpath_android.go ├── link_mpath_darwin.go ├── link_mpath_linux.go ├── link_mpath_other.go ├── link_other.go ├── link_sctp_linux.go ├── link_socks.go ├── link_tcp.go ├── link_tcp_darwin.go ├── link_tcp_linux.go ├── link_tcp_other.go ├── link_tls.go ├── link_unix.go ├── nodeinfo.go ├── options.go ├── proto.go ├── types.go └── version.go ├── defaults ├── defaults.go ├── defaults_darwin.go ├── defaults_freebsd.go ├── defaults_linux.go ├── defaults_openbsd.go ├── defaults_other.go └── defaults_windows.go ├── ipv6rwc ├── icmpv6.go └── ipv6rwc.go ├── multicast ├── admin.go ├── multicast.go ├── multicast_android.go ├── multicast_darwin.go ├── multicast_darwin_cgo.go ├── multicast_other.go ├── multicast_unix.go ├── multicast_windows.go └── options.go ├── restapi ├── IP2LOCATION-LITE-DB1.BIN └── rest_server.go ├── tun ├── admin.go ├── iface.go ├── options.go ├── tun.go ├── tun_bsd.go ├── tun_darwin.go ├── tun_linux.go ├── tun_other.go └── tun_windows.go └── version └── version.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: RiV-mesh 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | lint: 15 | name: Lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.19 21 | # - uses: actions/checkout@v3 22 | # - name: golangci-lint 23 | # uses: golangci/golangci-lint-action@v3 24 | # with: 25 | # args: --issues-exit-code=1 26 | 27 | codeql: 28 | name: Analyse 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | - name: Initialize CodeQL 40 | uses: github/codeql-action/init@v3 41 | with: 42 | languages: go 43 | 44 | - name: Autobuild 45 | uses: github/codeql-action/autobuild@v3 46 | 47 | - name: Perform CodeQL Analysis 48 | uses: github/codeql-action/analyze@v3 49 | 50 | build-linux: 51 | strategy: 52 | fail-fast: false 53 | matrix: 54 | goversion: ["1.21", "1.22"] 55 | 56 | name: Build & Test (Linux, Go ${{ matrix.goversion }}) 57 | needs: [lint] 58 | 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v4 62 | 63 | - name: Install webkit2gtk-4.0 64 | uses: awalsh128/cache-apt-pkgs-action@latest 65 | with: 66 | packages: libwebkit2gtk-4.0-dev gtk+-3.0 webkit2gtk-4.0 67 | version: 1.0 68 | 69 | - name: Set up Go 70 | uses: actions/setup-go@v5 71 | with: 72 | go-version: ${{ matrix.goversion }} 73 | 74 | - name: Build RiV-mesh 75 | run: go build -v ./... 76 | 77 | - name: Unit tests 78 | run: go test -v ./... 79 | 80 | build-windows: 81 | strategy: 82 | fail-fast: false 83 | matrix: 84 | goversion: ["1.22"] 85 | 86 | name: Build & Test (Windows, Go ${{ matrix.goversion }}) 87 | needs: [lint] 88 | 89 | runs-on: windows-latest 90 | steps: 91 | - uses: actions/checkout@v4 92 | 93 | - name: Set up Go 94 | uses: actions/setup-go@v5 95 | with: 96 | go-version: ${{ matrix.goversion }} 97 | 98 | - name: Build RiV-mesh 99 | run: go build -v ./... 100 | 101 | - name: Unit tests 102 | run: go test -v ./... 103 | 104 | build-macos: 105 | strategy: 106 | fail-fast: false 107 | matrix: 108 | goversion: ["1.22"] 109 | 110 | name: Build & Test (macOS, Go ${{ matrix.goversion }}) 111 | needs: [lint] 112 | 113 | runs-on: macos-latest 114 | steps: 115 | - uses: actions/checkout@v4 116 | 117 | - name: Set up Go 118 | uses: actions/setup-go@v5 119 | with: 120 | go-version: ${{ matrix.goversion }} 121 | 122 | - name: Build RiV-mesh 123 | run: go build -v ./... 124 | 125 | - name: Unit tests 126 | run: go test -v ./... 127 | 128 | tests-ok: 129 | name: All tests passed 130 | needs: [lint, codeql, build-linux, build-windows, build-macos] 131 | runs-on: ubuntu-latest 132 | if: ${{ !cancelled() }} 133 | steps: 134 | - name: Check all tests passed 135 | uses: re-actors/alls-green@release/v1 136 | with: 137 | jobs: ${{ toJSON(needs) }} 138 | -------------------------------------------------------------------------------- /.github/workflows/pkg.yml: -------------------------------------------------------------------------------- 1 | name: Packages 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | 8 | build-libs-android: 9 | 10 | name: Build libs (Android) 11 | runs-on: ubuntu-22.04 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | fetch-tags: true 19 | 20 | - name: 🐼 Setup go1.22+ 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: '>=1.22' 24 | 25 | - name: Run build command 26 | run: | 27 | go install golang.org/x/mobile/cmd/gomobile@latest 28 | gomobile init 29 | ./contrib/mobile/build -a 30 | 31 | - name: Get version 32 | id: get_version 33 | run: | 34 | PKGVERSION=$(sh -c './contrib/semver/version.sh --bare') 35 | echo "PKGVERSION=$PKGVERSION" >> $GITHUB_ENV 36 | 37 | - name: Upload .aar files 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: aar-files 41 | path: '**/*.aar' 42 | 43 | - name: Upload .jar files 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: jar-files 47 | path: '**/*.jar' 48 | 49 | - name: Publish aar library to Maven repo 50 | uses: RiV-chain/copy-local-file-maven-action@main 51 | with: 52 | artifact_repo: RiV-chain/artifact 53 | artifact_path: mesh.aar 54 | artifact_source_path: mesh-sources.jar 55 | gh_pat: ${{ secrets.MAVEN_PAT }} 56 | artifact_id: mesh 57 | group_id: org.rivchain 58 | version: ${{ env.PKGVERSION }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | built 3 | contrib/ui/mesh-ui/ui/doc/swagger.yaml 4 | *.syso 5 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | build-tags: 3 | - lint 4 | issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds 5 | skip-dirs: 6 | - contrib/ 7 | - misc/ 8 | linters: 9 | disable: 10 | - gocyclo -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch file", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "cmd/mesh/main.go", 13 | "args": [ 14 | "-useconffile", 15 | "/etc/mesh.conf", 16 | ], 17 | "console": "integratedTerminal", 18 | "asRoot": true, 19 | "env": { 20 | "PATH": "${env:PATH}" 21 | } 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | contrib/docker/Dockerfile 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the LGPLv3, included below. 2 | 3 | As a special exception to the GNU Lesser General Public License version 3 4 | ("LGPL3"), the copyright holders of this Library give you permission to 5 | convey to a third party a Combined Work that links statically or dynamically 6 | to this Library without providing any Minimal Corresponding Source or 7 | Minimal Application Code as set out in 4d or providing the installation 8 | information set out in section 4e, provided that you comply with the other 9 | provisions of LGPL3 and provided that you meet, for the Application the 10 | terms and conditions of the license(s) which apply to the Application. 11 | 12 | Except as stated in this special exception, the provisions of LGPL3 will 13 | continue to comply in full to this Library. If you modify this Library, you 14 | may apply this exception to your version of this Library, but you are not 15 | obliged to do so. If you do not wish to do so, delete this exception 16 | statement from your version. This exception does not (and cannot) modify any 17 | license terms which apply to the Application, with which you must still 18 | comply. 19 | 20 | GNU LESSER GENERAL PUBLIC LICENSE 21 | Version 3, 29 June 2007 22 | 23 | Copyright (C) 2007 Free Software Foundation, Inc. 24 | Everyone is permitted to copy and distribute verbatim copies 25 | of this license document, but changing it is not allowed. 26 | 27 | 28 | This version of the GNU Lesser General Public License incorporates 29 | the terms and conditions of version 3 of the GNU General Public 30 | License, supplemented by the additional permissions listed below. 31 | 32 | 0. Additional Definitions. 33 | 34 | As used herein, "this License" refers to version 3 of the GNU Lesser 35 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 36 | General Public License. 37 | 38 | "The Library" refers to a covered work governed by this License, 39 | other than an Application or a Combined Work as defined below. 40 | 41 | An "Application" is any work that makes use of an interface provided 42 | by the Library, but which is not otherwise based on the Library. 43 | Defining a subclass of a class defined by the Library is deemed a mode 44 | of using an interface provided by the Library. 45 | 46 | A "Combined Work" is a work produced by combining or linking an 47 | Application with the Library. The particular version of the Library 48 | with which the Combined Work was made is also called the "Linked 49 | Version". 50 | 51 | The "Minimal Corresponding Source" for a Combined Work means the 52 | Corresponding Source for the Combined Work, excluding any source code 53 | for portions of the Combined Work that, considered in isolation, are 54 | based on the Application, and not on the Linked Version. 55 | 56 | The "Corresponding Application Code" for a Combined Work means the 57 | object code and/or source code for the Application, including any data 58 | and utility programs needed for reproducing the Combined Work from the 59 | Application, but excluding the System Libraries of the Combined Work. 60 | 61 | 1. Exception to Section 3 of the GNU GPL. 62 | 63 | You may convey a covered work under sections 3 and 4 of this License 64 | without being bound by section 3 of the GNU GPL. 65 | 66 | 2. Conveying Modified Versions. 67 | 68 | If you modify a copy of the Library, and, in your modifications, a 69 | facility refers to a function or data to be supplied by an Application 70 | that uses the facility (other than as an argument passed when the 71 | facility is invoked), then you may convey a copy of the modified 72 | version: 73 | 74 | a) under this License, provided that you make a good faith effort to 75 | ensure that, in the event an Application does not supply the 76 | function or data, the facility still operates, and performs 77 | whatever part of its purpose remains meaningful, or 78 | 79 | b) under the GNU GPL, with none of the additional permissions of 80 | this License applicable to that copy. 81 | 82 | 3. Object Code Incorporating Material from Library Header Files. 83 | 84 | The object code form of an Application may incorporate material from 85 | a header file that is part of the Library. You may convey such object 86 | code under terms of your choice, provided that, if the incorporated 87 | material is not limited to numerical parameters, data structure 88 | layouts and accessors, or small macros, inline functions and templates 89 | (ten or fewer lines in length), you do both of the following: 90 | 91 | a) Give prominent notice with each copy of the object code that the 92 | Library is used in it and that the Library and its use are 93 | covered by this License. 94 | 95 | b) Accompany the object code with a copy of the GNU GPL and this license 96 | document. 97 | 98 | 4. Combined Works. 99 | 100 | You may convey a Combined Work under terms of your choice that, 101 | taken together, effectively do not restrict modification of the 102 | portions of the Library contained in the Combined Work and reverse 103 | engineering for debugging such modifications, if you also do each of 104 | the following: 105 | 106 | a) Give prominent notice with each copy of the Combined Work that 107 | the Library is used in it and that the Library and its use are 108 | covered by this License. 109 | 110 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 111 | document. 112 | 113 | c) For a Combined Work that displays copyright notices during 114 | execution, include the copyright notice for the Library among 115 | these notices, as well as a reference directing the user to the 116 | copies of the GNU GPL and this license document. 117 | 118 | d) Do one of the following: 119 | 120 | 0) Convey the Minimal Corresponding Source under the terms of this 121 | License, and the Corresponding Application Code in a form 122 | suitable for, and under terms that permit, the user to 123 | recombine or relink the Application with a modified version of 124 | the Linked Version to produce a modified Combined Work, in the 125 | manner specified by section 6 of the GNU GPL for conveying 126 | Corresponding Source. 127 | 128 | 1) Use a suitable shared library mechanism for linking with the 129 | Library. A suitable mechanism is one that (a) uses at run time 130 | a copy of the Library already present on the user's computer 131 | system, and (b) will operate properly with a modified version 132 | of the Library that is interface-compatible with the Linked 133 | Version. 134 | 135 | e) Provide Installation Information, but only if you would otherwise 136 | be required to provide such information under section 6 of the 137 | GNU GPL, and only to the extent that such information is 138 | necessary to install and execute a modified version of the 139 | Combined Work produced by recombining or relinking the 140 | Application with a modified version of the Linked Version. (If 141 | you use option 4d0, the Installation Information must accompany 142 | the Minimal Corresponding Source and Corresponding Application 143 | Code. If you use option 4d1, you must provide the Installation 144 | Information in the manner specified by section 6 of the GNU GPL 145 | for conveying Corresponding Source.) 146 | 147 | 5. Combined Libraries. 148 | 149 | You may place library facilities that are a work based on the 150 | Library side by side in a single library together with other library 151 | facilities that are not Applications and are not covered by this 152 | License, and convey such a combined library under terms of your 153 | choice, if you do both of the following: 154 | 155 | a) Accompany the combined library with a copy of the same work based 156 | on the Library, uncombined with any other library facilities, 157 | conveyed under the terms of this License. 158 | 159 | b) Give prominent notice with the combined library that part of it 160 | is a work based on the Library, and explaining where to find the 161 | accompanying uncombined form of the same work. 162 | 163 | 6. Revised Versions of the GNU Lesser General Public License. 164 | 165 | The Free Software Foundation may publish revised and/or new versions 166 | of the GNU Lesser General Public License from time to time. Such new 167 | versions will be similar in spirit to the present version, but may 168 | differ in detail to address new problems or concerns. 169 | 170 | Each version is given a distinguishing version number. If the 171 | Library as you received it specifies that a certain numbered version 172 | of the GNU Lesser General Public License "or any later version" 173 | applies to it, you have the option of following the terms and 174 | conditions either of that published version or of any later version 175 | published by the Free Software Foundation. If the Library as you 176 | received it does not specify a version number of the GNU Lesser 177 | General Public License, you may choose any version of the GNU Lesser 178 | General Public License ever published by the Free Software Foundation. 179 | 180 | If the Library as you received it specifies that a proxy can decide 181 | whether future versions of the GNU Lesser General Public License shall 182 | apply, that proxy's public statement of acceptance of any version is 183 | permanent authorization for you to choose that version for the 184 | Library. 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RiV-mesh first self arranging mesh network with link aggregation. 2 | 3 | ## Introduction 4 | 5 | RiV-mesh is an implementation of a fully end-to-end encrypted IPv6 6 | network, created in the scope to produce the Transport Layer for RiV Chain Blockchain, 7 | also to facilitate secure conectivity between a wide spectrum of endpoint devices like IoT devices, 8 | desktop computers or even routers. 9 | It is lightweight, self-arranging, supported on multiple 10 | platforms and allows pretty much any IPv6-capable application 11 | to communicate securely with other RiV-mesh nodes. 12 | RiV-mesh does not require you to have IPv6 Internet connectivity - it also works over IPv4. 13 | 14 | ## Supported Platforms 15 | 16 | RiV-mesh works on a number of platforms, including Linux, macOS, Ubiquiti 17 | EdgeRouter, VyOS, Windows, FreeBSD, OpenBSD and OpenWrt. 18 | 19 | Please see our [Installation](https://github.com/RiV-chain/RiV-mesh-builds#riv-mesh-build) 20 | page for more information. You may also find other platform-specific wrappers, scripts 21 | or tools in the `contrib` folder. 22 | 23 | ## Building 24 | 25 | If you want to build from source, as opposed to installing one of the pre-built 26 | packages: 27 | 28 | 1. Install [Go](https://golang.org) (requires Go 1.19 or later) 29 | 2. Clone this repository 30 | 2. Run `./build` 31 | 32 | Note that you can cross-compile for other platforms and architectures by 33 | specifying the `GOOS` and `GOARCH` environment variables, e.g. `GOOS=windows 34 | ./build` or `GOOS=linux GOARCH=mipsle ./build` 35 | 36 | ... or generate an iOS framework with: 37 | 38 | ``` 39 | ./contrib/mobile/build -i 40 | ``` 41 | 42 | ... or generate an Android AAR bundle with: 43 | 44 | ``` 45 | ./contrib/mobile/build -a 46 | ``` 47 | 48 | Other OS packages can be built in this repo: https://github.com/RiV-chain/RiV-mesh-builds. 49 | 50 | ## Running 51 | 52 | ### Generate configuration 53 | 54 | To generate static configuration, either generate a HJSON file (human-friendly, 55 | complete with comments): 56 | 57 | ``` 58 | ./mesh -genconf > /path/to/mesh.conf 59 | ``` 60 | 61 | ... or generate a plain JSON file (which is easy to manipulate 62 | programmatically): 63 | 64 | ``` 65 | ./mesh -genconf -json > /path/to/mesh.conf 66 | ``` 67 | 68 | You will need to edit the `mesh.conf` file to add or remove peers, modify 69 | other configuration such as listen addresses or multicast addresses, etc. 70 | 71 | ### Run RiV-mesh 72 | 73 | To run with the generated static configuration: 74 | 75 | ``` 76 | ./mesh -useconffile /path/to/mesh.conf 77 | ``` 78 | 79 | To run in auto-configuration mode (which will use sane defaults and random keys 80 | at each startup, instead of using a static configuration file): 81 | 82 | ``` 83 | ./mesh -autoconf 84 | ``` 85 | 86 | You will likely need to run RiV-mesh as a privileged user or under `sudo`, 87 | unless you have permission to create TUN/TAP adapters. On Linux this can be done 88 | by giving the RiV-mesh binary the `CAP_NET_ADMIN` capability. 89 | 90 | ## Documentation 91 | 92 | Documentation is available [on our website](https://riv-chain.github.io/RiV-mesh/). 93 | 94 | - [Installing RiV-mesh](https://riv-chain.github.io/RiV-mesh/) 95 | - [Configuring RiV-mesh](https://riv-chain.github.io/RiV-mesh/) 96 | - [Frequently asked questions](https://riv-chain.github.io/RiV-mesh/) 97 | - [Version changelog](CHANGELOG.md) 98 | 99 | ## Work in progress: 100 | 101 | DDNS 102 | 103 | ## Community 104 | 105 | Feel free to join us on our [Telegram 106 | channel](https://t.me/rivchain). 107 | 108 | ## Public peers 109 | If you are operating RiV-mesh peer and may create your pool request with your new per or use existing one https://github.com/RiV-chain/public-peers 110 | 111 | ## Known issues 112 | 113 | ### 1. Log message: 114 | ``` 115 | An error occurred starting multicast: listen udp6 [::]:9001: socket: address family not supported by protocol 116 | ``` 117 | and 118 | ``` 119 | An error occurred starting TUN/TAP: operation not supported 120 | ``` 121 | 122 | ### Caused by: 123 | The device has no IPv6 support 124 | 125 | 126 | ### 2. Log message: 127 | ``` 128 | An error occurred starting TUN/TAP: permission denied 129 | ``` 130 | 131 | ### Caused by: 132 | IPv6 support is not enabled. See the solution: https://github.com/yggdrasil-network/yggdrasil-go/issues/479#issuecomment-519512395 133 | 134 | ### 3. Mesh infinite output in log: 135 | Connected SCTP ... 136 | 137 | Disconnected SCTP ... 138 | 139 | ### Caused by: 140 | Docker interface docker0 is conflicting with SCTP bind process. The issue can be resolved by removing docker. 141 | 142 | ## License 143 | 144 | This code is released under the terms of the LGPLv3, but with an added exception 145 | that was shamelessly taken from [godeb](https://github.com/niemeyer/godeb). 146 | Under certain circumstances, this exception permits distribution of binaries 147 | that are (statically or dynamically) linked with this code, without requiring 148 | the distribution of Minimal Corresponding Source or Minimal Application Code. 149 | For more details, see: [LICENSE](LICENSE). 150 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | os: Visual Studio 2022 5 | shallow_clone: false 6 | 7 | environment: 8 | MSYSTEM: MINGW64 9 | MSYS2_PATH_TYPE: inherit 10 | CHERE_INVOKING: enabled_from_arguments 11 | GO111MODULE: on 12 | GOPATH: c:\gopath 13 | 14 | stack: go 1.19 15 | 16 | build_script: 17 | - cmd: >- 18 | cd %APPVEYOR_BUILD_FOLDER% 19 | ##### MinGW build 20 | - set OPT_PATH=C:\msys64\mingw32\bin;C:\msys64\mingw64\bin; 21 | - set PATH=%GOPATH%\bin;%OPT_PATH%%PATH% 22 | - go install github.com/tc-hib/go-winres@latest 23 | - go install github.com/swaggo/swag/cmd/swag@latest 24 | - swag init -g src/restapi/rest_server.go --ot yaml -o contrib/ui/mesh-ui/ui/doc 25 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" 26 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" 27 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi-gui.sh x64" 28 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi-gui.sh x86" 29 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi-ie.sh x64" 30 | - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi-ie.sh x86" 31 | 32 | test: off 33 | 34 | artifacts: 35 | - path: '*.msi' 36 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "/home/sl/.nvm/versions/node/v17.9.0/lib/node_modules/@babel/preset-env", 5 | { 6 | "useBuiltIns": "entry", 7 | "corejs": "3" 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ef 3 | 4 | PKGSRC=${PKGSRC:-github.com/RiV-chain/RiV-mesh/src/version} 5 | PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} 6 | PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} 7 | echo "Building: $PKGVER" 8 | 9 | if [ "$LDFLAGS" ]; then 10 | LDFLAGS="$LDFLAGS -X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" 11 | else 12 | LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" 13 | fi 14 | ARGS="-v" 15 | 16 | TARGET_PATH=$(pwd) 17 | while getopts "utc:l:dro:psg:b:" option 18 | do 19 | case "$option" 20 | in 21 | u) UPX=true;; 22 | t) TABLES=true;; 23 | c) GCFLAGS="$GCFLAGS $OPTARG";; 24 | l) LDFLAGS="$LDFLAGS $OPTARG";; 25 | d) ARGS="$ARGS -tags debug" DEBUG=true;; 26 | r) ARGS="$ARGS -race";; 27 | o) ARGS="$ARGS -o $OPTARG";; 28 | p) ARGS="$ARGS -buildmode=pie";; 29 | # statically linked executable 30 | s) STATIC=" -linkmode external -extldflags=-static";; 31 | # build target 32 | g) TARGET=$OPTARG;; 33 | # build path 34 | b) TARGET_PATH=$OPTARG;; 35 | esac 36 | done 37 | 38 | if [ -z $TABLES ] && [ -z $DEBUG ]; then 39 | LDFLAGS="$LDFLAGS -s -w" 40 | fi 41 | 42 | #could be static 43 | buildbin() { 44 | local CMD=$(realpath $1) 45 | echo "Building: $CMD for $GOOS-$GOARCH" 46 | 47 | (cd "$TARGET_PATH" && go build $ARGS -ldflags "${LDFLAGS}${LDFLAGS2}" -gcflags "$GCFLAGS" "$CMD") 48 | 49 | if [ $UPX ]; then 50 | upx --brute "$CMD" 51 | fi 52 | } 53 | 54 | build_mesh() { 55 | LDFLAGS2="${STATIC}" buildbin ./cmd/mesh 56 | } 57 | 58 | build_meshctl() { 59 | LDFLAGS2="${STATIC}" buildbin ./cmd/meshctl 60 | } 61 | 62 | case $TARGET in 63 | "mesh") 64 | build_mesh 65 | ;; 66 | "meshctl") 67 | build_meshctl 68 | ;; 69 | *) 70 | build_mesh 71 | build_meshctl 72 | ;; 73 | esac 74 | -------------------------------------------------------------------------------- /clean: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git clean -dxf 3 | -------------------------------------------------------------------------------- /cmd/genkeys/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This file generates crypto keys. 4 | It prints out a new set of keys each time if finds a "better" one. 5 | By default, "better" means a higher NodeID (-> higher IP address). 6 | This is because the IP address format can compress leading 1s in the address, to increase the number of ID bits in the address. 7 | 8 | If run with the "-sig" flag, it generates signing keys instead. 9 | A "better" signing key means one with a higher TreeID. 10 | This only matters if it's high enough to make you the root of the tree. 11 | 12 | */ 13 | package main 14 | 15 | import ( 16 | "crypto/ed25519" 17 | "encoding/hex" 18 | "fmt" 19 | "net" 20 | "os" 21 | "runtime" 22 | 23 | "github.com/gologme/log" 24 | 25 | c "github.com/RiV-chain/RiV-mesh/src/core" 26 | ) 27 | 28 | type keySet struct { 29 | priv ed25519.PrivateKey 30 | pub ed25519.PublicKey 31 | } 32 | 33 | func main() { 34 | threads := runtime.GOMAXPROCS(0) 35 | var currentBest ed25519.PublicKey 36 | newKeys := make(chan keySet, threads) 37 | for i := 0; i < threads; i++ { 38 | go doKeys(newKeys) 39 | } 40 | for { 41 | newKey := <-newKeys 42 | if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 { 43 | currentBest = newKey.pub 44 | fmt.Println("-----") 45 | fmt.Println("Priv:", hex.EncodeToString(newKey.priv)) 46 | fmt.Println("Pub:", hex.EncodeToString(newKey.pub)) 47 | logger := log.New(os.Stdout, "", log.Flags()) 48 | core, _ := c.New(newKey.priv, logger, nil) 49 | addr := core.AddrForKey(newKey.pub) 50 | fmt.Println("IP:", net.IP(addr[:]).String()) 51 | } 52 | } 53 | } 54 | 55 | func isBetter(oldPub, newPub ed25519.PublicKey) bool { 56 | for idx := range oldPub { 57 | if newPub[idx] < oldPub[idx] { 58 | return true 59 | } 60 | if newPub[idx] > oldPub[idx] { 61 | break 62 | } 63 | } 64 | return false 65 | } 66 | 67 | func doKeys(out chan<- keySet) { 68 | bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize) 69 | for idx := range bestKey { 70 | bestKey[idx] = 0xff 71 | } 72 | for { 73 | pub, priv, err := ed25519.GenerateKey(nil) 74 | if err != nil { 75 | panic(err) 76 | } 77 | if !isBetter(bestKey, pub) { 78 | continue 79 | } 80 | bestKey = pub 81 | out <- keySet{priv, pub} 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cmd/meshctl/cmd_line_env.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | "github.com/hjson/hjson-go" 11 | "golang.org/x/text/encoding/unicode" 12 | 13 | "github.com/RiV-chain/RiV-mesh/src/defaults" 14 | ) 15 | 16 | type CmdLineEnv struct { 17 | args []string 18 | endpoint, server string 19 | injson, ver bool 20 | } 21 | 22 | func newCmdLineEnv() CmdLineEnv { 23 | var cmdLineEnv CmdLineEnv 24 | cmdLineEnv.endpoint = defaults.Define().DefaultHttpAddress 25 | return cmdLineEnv 26 | } 27 | 28 | func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { 29 | flag.Usage = func() { 30 | fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) 31 | fmt.Println("Options:") 32 | flag.PrintDefaults() 33 | fmt.Println() 34 | fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.") 35 | fmt.Println() 36 | fmt.Println("Commands:\n - Use \"list\" for a list of available commands") 37 | fmt.Println() 38 | fmt.Println("Examples:") 39 | fmt.Println(" - ", os.Args[0], "list") 40 | fmt.Println(" - ", os.Args[0], "peers") 41 | fmt.Println(" - ", os.Args[0], "-v self") 42 | fmt.Println(" - ", os.Args[0], "-endpoint=http://localhost:19019 DHT") 43 | } 44 | 45 | server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint") 46 | injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") 47 | ver := flag.Bool("version", false, "Prints the version of this build") 48 | 49 | flag.Parse() 50 | 51 | cmdLineEnv.args = flag.Args() 52 | cmdLineEnv.server = *server 53 | cmdLineEnv.injson = *injson 54 | cmdLineEnv.ver = *ver 55 | } 56 | 57 | func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) { 58 | if cmdLineEnv.server == cmdLineEnv.endpoint { 59 | if config, err := os.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { 60 | if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || 61 | bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { 62 | utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) 63 | decoder := utf.NewDecoder() 64 | config, err = decoder.Bytes(config) 65 | if err != nil { 66 | panic(err) 67 | } 68 | } 69 | var dat map[string]interface{} 70 | if err := hjson.Unmarshal(config, &dat); err != nil { 71 | panic(err) 72 | } 73 | if ep, ok := dat["HttpAddress"].(string); ok && (ep != "none" && ep != "") { 74 | cmdLineEnv.endpoint = ep 75 | logger.Println("Found platform default config file", defaults.Define().DefaultHttpAddress) 76 | logger.Println("Using endpoint", cmdLineEnv.endpoint, "from HttpAddress") 77 | } else { 78 | logger.Println("Configuration file doesn't contain appropriate HttpAddress option") 79 | logger.Println("Falling back to platform default", defaults.Define().DefaultHttpAddress) 80 | } 81 | } else { 82 | logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile) 83 | logger.Println("Falling back to platform default", defaults.Define().DefaultHttpAddress) 84 | } 85 | } else { 86 | cmdLineEnv.endpoint = cmdLineEnv.server 87 | logger.Println("Using endpoint", cmdLineEnv.endpoint, "from command line") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cmd/meshctl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | 13 | "github.com/RiV-chain/RiV-mesh/src/version" 14 | ) 15 | 16 | func main() { 17 | // makes sure we can use defer and still return an error code to the OS 18 | os.Exit(run()) 19 | } 20 | 21 | func run() int { 22 | logbuffer := &bytes.Buffer{} 23 | logger := log.New(logbuffer, "", log.Flags()) 24 | 25 | defer func() int { 26 | if r := recover(); r != nil { 27 | logger.Println("Fatal error:", r) 28 | fmt.Print(logbuffer) 29 | return 1 30 | } 31 | return 0 32 | }() 33 | 34 | cmdLineEnv := newCmdLineEnv() 35 | cmdLineEnv.parseFlagsAndArgs() 36 | 37 | if cmdLineEnv.ver { 38 | fmt.Println("Build name:", version.BuildName()) 39 | fmt.Println("Build version:", version.BuildVersion()) 40 | fmt.Println("To get the version number of the running Mesh node, run", os.Args[0], "getSelf") 41 | return 0 42 | } 43 | 44 | if len(cmdLineEnv.args) == 0 { 45 | flag.Usage() 46 | return 0 47 | } 48 | 49 | cmdLineEnv.setEndpoint(logger) 50 | 51 | u, err := url.Parse(cmdLineEnv.endpoint) 52 | 53 | if err == nil { 54 | var response *http.Response 55 | var err error 56 | if cmdLineEnv.injson { 57 | response, err = http.Get(u.String() + "/api/" + cmdLineEnv.args[0]) 58 | } else { 59 | response, err = http.Get(u.String() + "/api/" + cmdLineEnv.args[0] + "?fmt=table") 60 | } 61 | if err != nil { 62 | panic(err) 63 | } 64 | result, err := io.ReadAll(response.Body) 65 | if err != nil { 66 | panic(err) 67 | } 68 | fmt.Println(string(result)) 69 | } else { 70 | panic(err) 71 | } 72 | 73 | return 0 74 | } 75 | -------------------------------------------------------------------------------- /contrib/mobile/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ef 4 | 5 | [ ! -d contrib/mobile ] && (echo "Must run ./contrib/mobile/build [-i] [-a] from the repository top level folder"; exit 1) 6 | 7 | PKGSRC=${PKGSRC:-github.com/RiV-chain/RiV-mesh/src/version} 8 | PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} 9 | PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} 10 | LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" 11 | ARGS="-v" 12 | 13 | while getopts "aitc:l:d" option 14 | do 15 | case "$option" 16 | in 17 | i) IOS=true;; 18 | a) ANDROID=true;; 19 | t) TABLES=true;; 20 | c) GCFLAGS="$GCFLAGS $OPTARG";; 21 | l) LDFLAGS="$LDFLAGS $OPTARG";; 22 | d) ARGS="$ARGS -tags debug" DEBUG=true;; 23 | esac 24 | done 25 | 26 | if [ -z $TABLES ] && [ -z $DEBUG ]; then 27 | LDFLAGS="$LDFLAGS -s -w" 28 | fi 29 | 30 | if [ ! $IOS ] && [ ! $ANDROID ]; then 31 | echo "Must specify -a (Android), -i (iOS) or both" 32 | exit 1 33 | fi 34 | 35 | if [ $IOS ]; then 36 | echo "Building framework for iOS" 37 | go get golang.org/x/mobile/bind 38 | gomobile bind \ 39 | -target ios -tags mobile -o Mesh.xcframework \ 40 | -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ 41 | ./contrib/mobile ./src/config; 42 | fi 43 | 44 | if [ $ANDROID ]; then 45 | echo "Building aar for Android" 46 | go get golang.org/x/mobile/bind 47 | gomobile bind \ 48 | -androidapi 21 -target android -tags mobile -o mesh.aar \ 49 | -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ 50 | ./contrib/mobile ./src/config; 51 | fi 52 | -------------------------------------------------------------------------------- /contrib/mobile/mobile.go: -------------------------------------------------------------------------------- 1 | package mobile 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "regexp" 9 | 10 | "github.com/gologme/log" 11 | 12 | //"github.com/RiV-chain/RiV-mesh/src/address" 13 | "github.com/RiV-chain/RiV-mesh/src/config" 14 | "github.com/RiV-chain/RiV-mesh/src/core" 15 | "github.com/RiV-chain/RiV-mesh/src/defaults" 16 | "github.com/RiV-chain/RiV-mesh/src/ipv6rwc" 17 | "github.com/RiV-chain/RiV-mesh/src/multicast" 18 | "github.com/RiV-chain/RiV-mesh/src/restapi" 19 | "github.com/RiV-chain/RiV-mesh/src/version" 20 | ) 21 | 22 | // RiV-mesh mobile package is meant to "plug the gap" for mobile support, as 23 | // Gomobile will not create headers for Swift/Obj-C etc if they have complex 24 | // (non-native) types. Therefore for iOS we will expose some nice simple 25 | // functions. Note that in the case of iOS we handle reading/writing to/from TUN 26 | // in Swift therefore we use the "dummy" TUN interface instead. 27 | 28 | type Mesh struct { 29 | core *core.Core 30 | iprwc *ipv6rwc.ReadWriteCloser 31 | config *config.NodeConfig 32 | multicast *multicast.Multicast 33 | rest_server *restapi.RestServer 34 | log MobileLogger 35 | } 36 | 37 | // StartAutoconfigure starts a node with a randomly generated config 38 | func (m *Mesh) StartAutoconfigure() error { 39 | return m.StartJSON([]byte("{}")) 40 | } 41 | 42 | // StartJSON starts a node with the given JSON config. You can get JSON config 43 | // (rather than HJSON) by using the GenerateConfigJSON() function 44 | func (m *Mesh) StartJSON(configjson []byte) error { 45 | logger := log.New(m.log, "", 0) 46 | logger.EnableLevel("error") 47 | logger.EnableLevel("warn") 48 | logger.EnableLevel("info") 49 | m.config = defaults.GenerateConfig() 50 | if err := json.Unmarshal(configjson, &m.config); err != nil { 51 | return err 52 | } 53 | // Setup the Mesh node itself. 54 | { 55 | sk, err := hex.DecodeString(m.config.PrivateKey) 56 | if err != nil { 57 | panic(err) 58 | } 59 | options := []core.SetupOption{ 60 | core.NodeInfo(m.config.NodeInfo), 61 | core.NodeInfoPrivacy(m.config.NodeInfoPrivacy), 62 | core.NetworkDomain(m.config.NetworkDomain), 63 | } 64 | for _, peer := range m.config.Peers { 65 | options = append(options, core.Peer{URI: peer}) 66 | } 67 | for intf, peers := range m.config.InterfacePeers { 68 | for _, peer := range peers { 69 | options = append(options, core.Peer{URI: peer, SourceInterface: intf}) 70 | } 71 | } 72 | for _, allowed := range m.config.AllowedPublicKeys { 73 | k, err := hex.DecodeString(allowed) 74 | if err != nil { 75 | panic(err) 76 | } 77 | options = append(options, core.AllowedPublicKey(k[:])) 78 | } 79 | m.core, err = core.New(sk[:], logger, options...) 80 | if err != nil { 81 | panic(err) 82 | } 83 | } 84 | 85 | // Setup the multicast module. 86 | if len(m.config.MulticastInterfaces) > 0 { 87 | var err error 88 | options := []multicast.SetupOption{} 89 | for _, intf := range m.config.MulticastInterfaces { 90 | options = append(options, multicast.MulticastInterface{ 91 | Regex: regexp.MustCompile(intf.Regex), 92 | Beacon: intf.Beacon, 93 | Listen: intf.Listen, 94 | Port: intf.Port, 95 | Priority: uint8(intf.Priority), 96 | }) 97 | } 98 | if m.multicast, err = multicast.New(m.core, logger, options...); err != nil { 99 | fmt.Println("Multicast module fail:", err) 100 | } else { 101 | logger.Infof("Multicast module started") 102 | } 103 | } 104 | 105 | // Setup the REST socket. 106 | { 107 | var err error 108 | if m.rest_server, err = restapi.NewRestServer(restapi.RestServerCfg{ 109 | Core: m.core, 110 | Multicast: m.multicast, 111 | Log: logger, 112 | ListenAddress: m.config.HttpAddress, 113 | WwwRoot: m.config.WwwRoot, 114 | ConfigFn: "", 115 | }); err != nil { 116 | logger.Errorln(err) 117 | } else { 118 | err = m.rest_server.Serve() 119 | if err != nil { 120 | logger.Errorln(err) 121 | } 122 | } 123 | } 124 | 125 | mtu := m.config.IfMTU 126 | m.iprwc = ipv6rwc.NewReadWriteCloser(m.core) 127 | if m.iprwc.MaxMTU() < mtu { 128 | mtu = m.iprwc.MaxMTU() 129 | } 130 | m.iprwc.SetMTU(mtu) 131 | return nil 132 | } 133 | 134 | // Send sends a packet to RiV-mesh. It should be a fully formed 135 | // IPv6 packet 136 | func (m *Mesh) Send(p []byte) error { 137 | if m.iprwc == nil { 138 | return nil 139 | } 140 | _, _ = m.iprwc.Write(p) 141 | return nil 142 | } 143 | 144 | // Send sends a packet from given buffer to RiV-mesh. From first byte up to length. 145 | func (m *Mesh) SendBuffer(p []byte, length int) error { 146 | if m.iprwc == nil { 147 | return nil 148 | } 149 | if len(p) < length { 150 | return nil 151 | } 152 | _, _ = m.iprwc.Write(p[:length]) 153 | return nil 154 | } 155 | 156 | // Recv waits for and reads a packet coming from RiV-mesh. It 157 | // will be a fully formed IPv6 packet 158 | func (m *Mesh) Recv() ([]byte, error) { 159 | if m.iprwc == nil { 160 | return nil, nil 161 | } 162 | var buf [65535]byte 163 | n, _ := m.iprwc.Read(buf[:]) 164 | return buf[:n], nil 165 | } 166 | 167 | // Recv waits for and reads a packet coming from RiV-mesh to given buffer, returning size of packet 168 | func (m *Mesh) RecvBuffer(buf []byte) (int, error) { 169 | if m.iprwc == nil { 170 | return 0, nil 171 | } 172 | n, _ := m.iprwc.Read(buf) 173 | return n, nil 174 | } 175 | 176 | // Stop the mobile Mesh instance 177 | func (m *Mesh) Stop() error { 178 | logger := log.New(m.log, "", 0) 179 | logger.EnableLevel("info") 180 | logger.Infof("Stop the mobile Mesh instance %s", "") 181 | if err := m.multicast.Stop(); err != nil { 182 | return err 183 | } 184 | m.core.Stop() 185 | m.rest_server.Shutdown() 186 | m.rest_server = nil 187 | return nil 188 | } 189 | 190 | // Retry resets the peer connection timer and tries to dial them immediately. 191 | func (m *Mesh) RetryPeersNow() { 192 | m.core.RetryPeersNow() 193 | } 194 | 195 | // GenerateConfigJSON generates mobile-friendly configuration in JSON format 196 | func GenerateConfigJSON() []byte { 197 | nc := defaults.GenerateConfig() 198 | nc.IfName = "none" 199 | if json, err := json.Marshal(nc); err == nil { 200 | return json 201 | } 202 | return nil 203 | } 204 | 205 | // GetAddressString gets the node's IPv6 address 206 | func (m *Mesh) GetAddressString() string { 207 | ip := m.core.Address() 208 | return ip.String() 209 | } 210 | 211 | // GetSubnetString gets the node's IPv6 subnet in CIDR notation 212 | func (m *Mesh) GetSubnetString() string { 213 | subnet := m.core.Subnet() 214 | return subnet.String() 215 | } 216 | 217 | // GetPublicKeyString gets the node's public key in hex form 218 | func (m *Mesh) GetPublicKeyString() string { 219 | return hex.EncodeToString(m.core.GetSelf().Key) 220 | } 221 | 222 | // GetCoordsString gets the node's coordinates 223 | func (m *Mesh) GetCoordsString() string { 224 | return fmt.Sprintf("%v", m.core.GetSelf().Coords) 225 | } 226 | 227 | func (m *Mesh) GetPeersJSON() (result string) { 228 | peers := []struct { 229 | core.PeerInfo 230 | IP string 231 | }{} 232 | for _, v := range m.core.GetPeers() { 233 | a := m.core.AddrForKey(v.Key) 234 | ip := net.IP(a[:]).String() 235 | peers = append(peers, struct { 236 | core.PeerInfo 237 | IP string 238 | }{ 239 | PeerInfo: v, 240 | IP: ip, 241 | }) 242 | } 243 | if res, err := json.Marshal(peers); err == nil { 244 | return string(res) 245 | } else { 246 | return "{}" 247 | } 248 | } 249 | 250 | func (m *Mesh) GetDHTJSON() (result string) { 251 | if res, err := json.Marshal(m.core.GetDHT()); err == nil { 252 | return string(res) 253 | } else { 254 | return "{}" 255 | } 256 | } 257 | 258 | // GetMTU returns the configured node MTU. This must be called AFTER Start. 259 | func (m *Mesh) GetMTU() int { 260 | return int(m.core.MTU()) 261 | } 262 | 263 | func GetVersion() string { 264 | return version.BuildVersion() 265 | } 266 | -------------------------------------------------------------------------------- /contrib/mobile/mobile_android.go: -------------------------------------------------------------------------------- 1 | //go:build android 2 | // +build android 3 | 4 | package mobile 5 | 6 | import "log" 7 | 8 | type MobileLogger struct{} 9 | 10 | func (nsl MobileLogger) Write(p []byte) (n int, err error) { 11 | log.Println(string(p)) 12 | return len(p), nil 13 | } 14 | -------------------------------------------------------------------------------- /contrib/mobile/mobile_ios.go: -------------------------------------------------------------------------------- 1 | //go:build ios 2 | // +build ios 3 | 4 | package mobile 5 | 6 | /* 7 | #cgo CFLAGS: -x objective-c 8 | #cgo LDFLAGS: -framework Foundation 9 | #import 10 | void Log(const char *text) { 11 | NSString *nss = [NSString stringWithUTF8String:text]; 12 | NSLog(@"%@", nss); 13 | } 14 | */ 15 | import "C" 16 | import ( 17 | "unsafe" 18 | ) 19 | 20 | type MobileLogger struct { 21 | } 22 | 23 | func (nsl MobileLogger) Write(p []byte) (n int, err error) { 24 | p = append(p, 0) 25 | cstr := (*C.char)(unsafe.Pointer(&p[0])) 26 | C.Log(cstr) 27 | return len(p), nil 28 | } 29 | -------------------------------------------------------------------------------- /contrib/mobile/mobile_other.go: -------------------------------------------------------------------------------- 1 | //go:build !android && !ios 2 | // +build !android,!ios 3 | 4 | package mobile 5 | 6 | import "fmt" 7 | 8 | type MobileLogger struct { 9 | } 10 | 11 | func (nsl MobileLogger) Write(p []byte) (n int, err error) { 12 | fmt.Print(string(p)) 13 | return len(p), nil 14 | } 15 | -------------------------------------------------------------------------------- /contrib/mobile/mobile_test.go: -------------------------------------------------------------------------------- 1 | package mobile 2 | 3 | import "testing" 4 | 5 | func TestStartMesh(t *testing.T) { 6 | mesh := &Mesh{} 7 | if err := mesh.StartAutoconfigure(); err != nil { 8 | t.Fatalf("Failed to start RiV-mesh: %s", err) 9 | } 10 | t.Log("Address:", mesh.GetAddressString()) 11 | t.Log("Subnet:", mesh.GetSubnetString()) 12 | t.Log("Coords:", mesh.GetCoordsString()) 13 | if err := mesh.Stop(); err != nil { 14 | t.Fatalf("Failed to stop RiV-mesh: %s", err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contrib/msi/msversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Get the last tag 4 | TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) 5 | 6 | # Did getting the tag succeed? 7 | if [ $? != 0 ] || [ -z "$TAG" ]; then 8 | printf -- "unknown" 9 | exit 0 10 | fi 11 | 12 | # Get the current branch 13 | BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) 14 | 15 | # Did getting the branch succeed? 16 | if [ $? != 0 ] || [ -z "$BRANCH" ]; then 17 | BRANCH="master" 18 | fi 19 | 20 | STAG=$(echo $TAG | sed 's/^v//' | sed 's/[^0123456789.].//') 21 | 22 | printf '%s' "$STAG" -------------------------------------------------------------------------------- /contrib/semver/name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Get the current branch name 4 | BRANCH="$GITHUB_REF_NAME" 5 | if [ -z "$BRANCH" ]; then 6 | BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) 7 | fi 8 | 9 | # Complain if the git history is not available 10 | if [ $? != 0 ] || [ -z "$BRANCH" ]; then 11 | printf "mesh" 12 | exit 0 13 | fi 14 | 15 | # Remove "/" characters from the branch name if present 16 | BRANCH=$(echo $BRANCH | tr -d "/") 17 | 18 | # Check if the branch name is not master 19 | if [ "$BRANCH" = "master" ]; then 20 | printf "mesh" 21 | exit 0 22 | fi 23 | 24 | # If it is something other than master, append it 25 | printf "mesh-%s" "$BRANCH" -------------------------------------------------------------------------------- /contrib/semver/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$*" in 4 | *--bare*) 5 | # Remove the "v" prefix 6 | git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | sed 's/^v//' 7 | ;; 8 | *) 9 | git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" 10 | ;; 11 | esac -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/RiV-chain/RiV-mesh 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 7 | github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 8 | github.com/getlantern/multipath v0.0.0-20220920195041-55195f38df73 9 | github.com/gologme/log v1.2.0 10 | github.com/hashicorp/go-syslog v1.0.0 11 | github.com/hjson/hjson-go v3.1.0+incompatible 12 | github.com/kardianos/minwinsvc v1.0.2 13 | github.com/mitchellh/mapstructure v1.4.1 14 | github.com/vikulin/sctp v0.0.0-20221009200520-ae0f2830e422 15 | github.com/vishvananda/netlink v1.1.0 16 | golang.org/x/net v0.7.0 17 | golang.org/x/sys v0.5.0 18 | golang.org/x/text v0.7.0 19 | golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a 20 | golang.zx2c4.com/wireguard/windows v0.5.3 21 | ) 22 | 23 | require gerace.dev/zipfs v0.2.0 24 | 25 | require ( 26 | github.com/slonm/tableprinter v0.0.0-20230107100804-643098716018 27 | github.com/vorot93/golang-signals v0.0.0-20170221070717-d9e83421ce45 28 | github.com/wlynxg/anet v0.0.4 29 | golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 30 | golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 31 | ) 32 | 33 | require github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 // indirect 34 | 35 | require ( 36 | github.com/dustin/go-humanize v1.0.0 // indirect 37 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect 38 | github.com/getlantern/ema v0.0.0-20190620044903-5943d28f40e4 // indirect 39 | github.com/getlantern/errors v1.0.1 // indirect 40 | github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873 // indirect 41 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect 42 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect 43 | github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect 44 | github.com/go-stack/stack v1.8.0 // indirect 45 | github.com/google/uuid v1.1.2 // indirect 46 | github.com/libp2p/go-buffer-pool v0.0.2 // indirect 47 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 48 | github.com/rivo/uniseg v0.3.4 // indirect 49 | go.uber.org/atomic v1.7.0 // indirect 50 | go.uber.org/multierr v1.6.0 // indirect 51 | go.uber.org/zap v1.19.1 // indirect 52 | golang.org/x/crypto v0.1.0 // indirect 53 | ) 54 | 55 | require ( 56 | github.com/ip2location/ip2location-go/v9 v9.5.0 57 | github.com/mattn/go-runewidth v0.0.13 // indirect 58 | github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /misc/run-schannel-netns: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Connects nodes in a network resembling an s-channel feynmann diagram. 4 | 5 | # 1 5 6 | # \ / 7 | # 3--4 8 | # / \ 9 | # 2 6 10 | 11 | # Bandwidth constraints are applied to 4<->5 and 4<->6. 12 | # The idea is to make sure that bottlenecks on one link don't affect the other. 13 | 14 | ip netns add node1 15 | ip netns add node2 16 | ip netns add node3 17 | ip netns add node4 18 | ip netns add node5 19 | ip netns add node6 20 | 21 | ip link add veth13 type veth peer name veth31 22 | ip link set veth13 netns node1 up 23 | ip link set veth31 netns node3 up 24 | 25 | ip link add veth23 type veth peer name veth32 26 | ip link set veth23 netns node2 up 27 | ip link set veth32 netns node3 up 28 | 29 | ip link add veth34 type veth peer name veth43 30 | ip link set veth34 netns node3 up 31 | ip link set veth43 netns node4 up 32 | 33 | ip link add veth45 type veth peer name veth54 34 | ip link set veth45 netns node4 up 35 | ip link set veth54 netns node5 up 36 | 37 | ip link add veth46 type veth peer name veth64 38 | ip link set veth46 netns node4 up 39 | ip link set veth64 netns node6 up 40 | 41 | ip netns exec node4 tc qdisc add dev veth45 root tbf rate 100mbit burst 8192 latency 1ms 42 | ip netns exec node5 tc qdisc add dev veth54 root tbf rate 100mbit burst 8192 latency 1ms 43 | 44 | ip netns exec node4 tc qdisc add dev veth46 root tbf rate 10mbit burst 8192 latency 1ms 45 | ip netns exec node6 tc qdisc add dev veth64 root tbf rate 10mbit burst 8192 latency 1ms 46 | 47 | ip netns exec node1 ip link set lo up 48 | ip netns exec node2 ip link set lo up 49 | ip netns exec node3 ip link set lo up 50 | ip netns exec node4 ip link set lo up 51 | ip netns exec node5 ip link set lo up 52 | ip netns exec node6 ip link set lo up 53 | 54 | echo '{AdminListen: "none"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 55 | echo '{AdminListen: "none"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 56 | echo '{AdminListen: "none"}' | ip netns exec node3 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 57 | echo '{AdminListen: "none"}' | ip netns exec node4 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 58 | echo '{AdminListen: "none"}' | ip netns exec node5 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 59 | echo '{AdminListen: "none"}' | ip netns exec node6 env PPROFLISTEN=localhost:6060 ./mesh --useconf &> /dev/null & 60 | 61 | echo "Started, to continue you should (possibly w/ sudo):" 62 | echo "kill" $(jobs -p) 63 | wait 64 | 65 | ip netns delete node1 66 | ip netns delete node2 67 | ip netns delete node3 68 | ip netns delete node4 69 | ip netns delete node5 70 | ip netns delete node6 71 | 72 | ip link delete veth13 73 | ip link delete veth23 74 | ip link delete veth34 75 | ip link delete veth45 76 | ip link delete veth46 77 | -------------------------------------------------------------------------------- /misc/run-twolink-test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Connects nodes in two namespaces by two links with different bandwidth (10mbit and 100mbit) 4 | 5 | ip netns add node1 6 | ip netns add node2 7 | 8 | ip link add veth11 type veth peer name veth21 9 | ip link set veth11 netns node1 up 10 | ip link set veth21 netns node2 up 11 | 12 | ip link add veth12 type veth peer name veth22 13 | ip link set veth12 netns node1 up 14 | ip link set veth22 netns node2 up 15 | 16 | ip netns exec node1 tc qdisc add dev veth11 root tbf rate 10mbit burst 8192 latency 1ms 17 | ip netns exec node2 tc qdisc add dev veth21 root tbf rate 10mbit burst 8192 latency 1ms 18 | 19 | ip netns exec node1 tc qdisc add dev veth12 root tbf rate 100mbit burst 8192 latency 1ms 20 | ip netns exec node2 tc qdisc add dev veth22 root tbf rate 100mbit burst 8192 latency 1ms 21 | 22 | echo '{AdminListen: "unix://node1.sock"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./mesh -logging "info,warn,error,debug" -useconf &> node1.log & 23 | echo '{AdminListen: "unix://node2.sock"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./mesh -logging "info,warn,error,debug" -useconf &> node2.log & 24 | 25 | echo "Started, to continue you should (possibly w/ sudo):" 26 | echo "kill" $(jobs -p) 27 | wait 28 | 29 | ip netns delete node1 30 | ip netns delete node2 31 | 32 | ip link delete veth11 33 | ip link delete veth12 34 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | The config package contains structures related to the configuration of an 3 | RiV-mesh node. 4 | 5 | The configuration contains, amongst other things, encryption keys which are used 6 | to derive a node's identity, information about peerings and node information 7 | that is shared with the network. There are also some module-specific options 8 | related to TUN, multicast and the admin socket. 9 | 10 | In order for a node to maintain the same identity across restarts, you should 11 | persist the configuration onto the filesystem or into some configuration storage 12 | so that the encryption keys (and therefore the node ID) do not change. 13 | 14 | Note that RiV-mesh will automatically populate sane defaults for any 15 | configuration option that is not provided. 16 | */ 17 | package config 18 | 19 | import ( 20 | "crypto/ed25519" 21 | "encoding/hex" 22 | ) 23 | 24 | // NodeConfig is the main configuration structure, containing configuration 25 | // options that are necessary for an RiV-mesh node to run. You will need to 26 | // supply one of these structs to the RiV-mesh core when starting a node. 27 | type NodeConfig struct { 28 | Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` 29 | InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` 30 | Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` 31 | AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for meshctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead.\nExamples: unix:///var/run/mesh.sock, tcp://localhost:9001."` 32 | HttpAddress string `comment:"Listen address for admin rest requests and web interface. Default is to listen for local\nconnections on TCP/19019. To start listening on tun IP use '' as domain name.\nTo disable the admin rest interface,\nuse the value \"none\" instead. Example: http://localhost:19019."` 33 | WwwRoot string `comment:"Points out to embedded webserver root folder path where web interface assets are located.\nExample:/apps/mesh/www."` 34 | MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` 35 | AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` 36 | PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` 37 | PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` 38 | IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` 39 | IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` 40 | NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and RiV-mesh version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` 41 | NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` 42 | NetworkDomain NetworkDomainConfig `comment:"Address prefix used by mesh.\nThe current implementation requires this to be a multiple of 8 bits + 7 bits.4\nNodes that configure this differently will be unable to communicate with each other using IP packets."` 43 | PublicPeersUrl string `comment:"Public peers URL which contains all peers in JSON format grouped by a country."` 44 | FeaturesConfig map[string]interface{} `comment:"Optional features config. This must be a { \"key\": \"value\", ... } map\not set as null. This is mandatory for extended featured builds containing features specific settings."` 45 | } 46 | 47 | type MulticastInterfaceConfig struct { 48 | Regex string 49 | Beacon bool 50 | Listen bool 51 | Port uint16 52 | Priority uint64 // really uint8, but gobind won't export it 53 | } 54 | 55 | type NetworkDomainConfig struct { 56 | Prefix string 57 | } 58 | 59 | // NewSigningKeys replaces the signing keypair in the NodeConfig with a new 60 | // signing keypair. The signing keys are used by the switch to derive the 61 | // structure of the spanning tree. 62 | func (cfg *NodeConfig) NewKeys() { 63 | spub, spriv, err := ed25519.GenerateKey(nil) 64 | if err != nil { 65 | panic(err) 66 | } 67 | cfg.PublicKey = hex.EncodeToString(spub[:]) 68 | cfg.PrivateKey = hex.EncodeToString(spriv[:]) 69 | } 70 | -------------------------------------------------------------------------------- /src/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | ) 8 | 9 | func TestConfig_Keys(t *testing.T) { 10 | var nodeConfig NodeConfig 11 | nodeConfig.NewKeys() 12 | 13 | publicKey1, err := hex.DecodeString(nodeConfig.PublicKey) 14 | 15 | if err != nil { 16 | t.Fatal("can not decode generated public key") 17 | } 18 | 19 | if len(publicKey1) == 0 { 20 | t.Fatal("empty public key generated") 21 | } 22 | 23 | privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey) 24 | 25 | if err != nil { 26 | t.Fatal("can not decode generated private key") 27 | } 28 | 29 | if len(privateKey1) == 0 { 30 | t.Fatal("empty private key generated") 31 | } 32 | 33 | nodeConfig.NewKeys() 34 | 35 | publicKey2, err := hex.DecodeString(nodeConfig.PublicKey) 36 | 37 | if err != nil { 38 | t.Fatal("can not decode generated public key") 39 | } 40 | 41 | if bytes.Equal(publicKey2, publicKey1) { 42 | t.Fatal("same public key generated") 43 | } 44 | 45 | privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey) 46 | 47 | if err != nil { 48 | t.Fatal("can not decode generated private key") 49 | } 50 | 51 | if bytes.Equal(privateKey2, privateKey1) { 52 | t.Fatal("same private key generated") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/core/address.go: -------------------------------------------------------------------------------- 1 | // Package address contains the types used by mesh to represent IPv6 addresses or prefixes, as well as functions for working with these types. 2 | // Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches. 3 | package core 4 | 5 | import ( 6 | "crypto/ed25519" 7 | "encoding/hex" 8 | ) 9 | 10 | // Address represents an IPv6 address in the mesh address range. 11 | type Address [16]byte 12 | 13 | // Subnet represents an IPv6 /64 subnet in the mesh subnet range. 14 | type Subnet [8]byte 15 | 16 | // GetPrefix returns the address prefix used by mesh. 17 | // The current implementation requires this to be a multiple of 8 bits + 7 bits. 18 | // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). 19 | // Nodes that configure this differently will be unable to communicate with each other using IP packets, though routing and the DHT machinery *should* still work. 20 | func (c *Core) GetPrefix() [1]byte { 21 | p, err := hex.DecodeString(c.config.networkdomain.Prefix) 22 | if err != nil { 23 | panic(err) 24 | } 25 | var prefix [1]byte 26 | copy(prefix[:], p[:1]) 27 | return prefix 28 | } 29 | 30 | // IsValid returns true if an address falls within the range used by nodes in the network. 31 | func (c *Core) IsValidAddress(a Address) bool { 32 | prefix := c.GetPrefix() 33 | for idx := range prefix { 34 | if a[idx] != prefix[idx] { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | 41 | // IsValid returns true if a prefix falls within the range usable by the network. 42 | func (c *Core) IsValidSubnet(s Subnet) bool { 43 | prefix := c.GetPrefix() 44 | l := len(prefix) 45 | for idx := range prefix[:l-1] { 46 | if s[idx] != prefix[idx] { 47 | return false 48 | } 49 | } 50 | return s[l-1] == prefix[l-1]|0x01 51 | } 52 | 53 | // AddrForKey takes an ed25519.PublicKey as an argument and returns an *Address. 54 | // This function returns nil if the key length is not ed25519.PublicKeySize. 55 | // This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address. 56 | // The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the public key. 57 | // The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. 58 | func (c *Core) AddrForKey(publicKey ed25519.PublicKey) *Address { 59 | // 128 bit address 60 | // Begins with prefix 61 | // Next bit is a 0 62 | // Next 7 bits, interpreted as a uint, are # of leading 1s in the NodeID 63 | // Leading 1s and first leading 0 of the NodeID are truncated off 64 | // The rest is appended to the IPv6 address (truncated to 128 bits total) 65 | if len(publicKey) != ed25519.PublicKeySize { 66 | return nil 67 | } 68 | var buf [ed25519.PublicKeySize]byte 69 | copy(buf[:], publicKey) 70 | for idx := range buf { 71 | buf[idx] = ^buf[idx] 72 | } 73 | var addr Address 74 | var temp = make([]byte, 0, 32) 75 | done := false 76 | ones := byte(0) 77 | bits := byte(0) 78 | nBits := 0 79 | for idx := 0; idx < 8*len(buf); idx++ { 80 | bit := (buf[idx/8] & (0x80 >> byte(idx%8))) >> byte(7-(idx%8)) 81 | if !done && bit != 0 { 82 | ones++ 83 | continue 84 | } 85 | if !done && bit == 0 { 86 | done = true 87 | continue // FIXME? this assumes that ones <= 127, probably only worth changing by using a variable length uint64, but that would require changes to the addressing scheme, and I'm not sure ones > 127 is realistic 88 | } 89 | bits = (bits << 1) | bit 90 | nBits++ 91 | if nBits == 8 { 92 | nBits = 0 93 | temp = append(temp, bits) 94 | } 95 | } 96 | prefix := c.GetPrefix() 97 | copy(addr[:], prefix[:]) 98 | addr[len(prefix)] = ones 99 | copy(addr[len(prefix)+1:], temp) 100 | return &addr 101 | } 102 | 103 | // SubnetForKey takes an ed25519.PublicKey as an argument and returns a *Subnet. 104 | // This function returns nil if the key length is not ed25519.PublicKeySize. 105 | // The subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. 106 | // The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the key. 107 | // The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. 108 | func (c *Core) SubnetForKey(publicKey ed25519.PublicKey) *Subnet { 109 | // Exactly as the address version, with two exceptions: 110 | // 1) The first bit after the fixed prefix is a 1 instead of a 0 111 | // 2) It's truncated to a subnet prefix length instead of 128 bits 112 | addr := c.AddrForKey(publicKey) 113 | if addr == nil { 114 | return nil 115 | } 116 | var snet Subnet 117 | copy(snet[:], addr[:]) 118 | prefix := c.GetPrefix() // nolint:staticcheck 119 | snet[len(prefix)-1] |= 0x01 120 | return &snet 121 | } 122 | 123 | // GetKet returns the partial ed25519.PublicKey for the Address. 124 | // This is used for key lookup. 125 | func (c *Core) GetAddressKey(a Address) ed25519.PublicKey { 126 | var key [ed25519.PublicKeySize]byte 127 | prefix := c.GetPrefix() // nolint:staticcheck 128 | ones := int(a[len(prefix)]) 129 | for idx := 0; idx < ones; idx++ { 130 | key[idx/8] |= 0x80 >> byte(idx%8) 131 | } 132 | keyOffset := ones + 1 133 | addrOffset := 8*len(prefix) + 8 134 | for idx := addrOffset; idx < 8*len(a); idx++ { 135 | bits := a[idx/8] & (0x80 >> byte(idx%8)) 136 | bits <<= byte(idx % 8) 137 | keyIdx := keyOffset + (idx - addrOffset) 138 | bits >>= byte(keyIdx % 8) 139 | idx := keyIdx / 8 140 | if idx >= len(key) { 141 | break 142 | } 143 | key[idx] |= bits 144 | } 145 | for idx := range key { 146 | key[idx] = ^key[idx] 147 | } 148 | return ed25519.PublicKey(key[:]) 149 | } 150 | 151 | // GetKet returns the partial ed25519.PublicKey for the Subnet. 152 | // This is used for key lookup. 153 | func (c *Core) GetSubnetKey(s Subnet) ed25519.PublicKey { 154 | var addr Address 155 | copy(addr[:], s[:]) 156 | return c.GetAddressKey(addr) 157 | } 158 | -------------------------------------------------------------------------------- /src/core/address_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | func (c *Core) TestAddress_Address_IsValid(t *testing.T) { 11 | var address Address 12 | rand.Read(address[:]) 13 | 14 | address[0] = 0 15 | 16 | if c.IsValidAddress(address) { 17 | t.Fatal("invalid address marked as valid") 18 | } 19 | 20 | address[0] = 0xfd 21 | 22 | if c.IsValidAddress(address) { 23 | t.Fatal("invalid address marked as valid") 24 | } 25 | 26 | address[0] = 0xfc 27 | 28 | if !c.IsValidAddress(address) { 29 | t.Fatal("valid address marked as invalid") 30 | } 31 | } 32 | 33 | func (c *Core) TestAddress_Subnet_IsValid(t *testing.T) { 34 | var subnet Subnet 35 | rand.Read(subnet[:]) 36 | 37 | subnet[0] = 0 38 | 39 | if c.IsValidSubnet(subnet) { 40 | t.Fatal("invalid subnet marked as valid") 41 | } 42 | 43 | subnet[0] = 0xfc 44 | 45 | if c.IsValidSubnet(subnet) { 46 | t.Fatal("invalid subnet marked as valid") 47 | } 48 | 49 | subnet[0] = 0xfd 50 | 51 | if !c.IsValidSubnet(subnet) { 52 | t.Fatal("valid subnet marked as invalid") 53 | } 54 | } 55 | 56 | func (c *Core) TestAddress_AddrForKey(t *testing.T) { 57 | publicKey := ed25519.PublicKey{ 58 | 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, 59 | 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, 60 | } 61 | 62 | expectedAddress := Address{ 63 | 0xfc, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, 64 | } 65 | 66 | if *c.AddrForKey(publicKey) != expectedAddress { 67 | t.Fatal("invalid address returned") 68 | } 69 | } 70 | 71 | func (c *Core) TestAddress_SubnetForKey(t *testing.T) { 72 | publicKey := ed25519.PublicKey{ 73 | 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, 74 | 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, 75 | } 76 | 77 | expectedSubnet := Subnet{0xfd, 0, 132, 138, 96, 79, 187, 126} 78 | 79 | if *c.SubnetForKey(publicKey) != expectedSubnet { 80 | t.Fatal("invalid subnet returned") 81 | } 82 | } 83 | 84 | func (c *Core) TestAddress_Address_GetKey(t *testing.T) { 85 | address := Address{ 86 | 0xfc, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, 87 | } 88 | 89 | expectedPublicKey := ed25519.PublicKey{ 90 | 189, 186, 207, 216, 34, 64, 222, 61, 91 | 205, 18, 57, 36, 203, 181, 127, 255, 92 | 255, 255, 255, 255, 255, 255, 255, 255, 93 | 255, 255, 255, 255, 255, 255, 255, 255, 94 | } 95 | 96 | if !bytes.Equal(c.GetAddressKey(address), expectedPublicKey) { 97 | t.Fatal("invalid public key returned") 98 | } 99 | } 100 | 101 | func (c *Core) TestAddress_Subnet_GetKey(t *testing.T) { 102 | subnet := Subnet{0xfd, 0, 132, 138, 96, 79, 187, 126} 103 | 104 | expectedPublicKey := ed25519.PublicKey{ 105 | 189, 186, 207, 216, 34, 64, 255, 255, 106 | 255, 255, 255, 255, 255, 255, 255, 255, 107 | 255, 255, 255, 255, 255, 255, 255, 255, 108 | 255, 255, 255, 255, 255, 255, 255, 255, 109 | } 110 | 111 | if !bytes.Equal(c.GetSubnetKey(subnet), expectedPublicKey) { 112 | t.Fatal("invalid public key returned") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/core/api.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "fmt" 6 | "sync/atomic" 7 | "time" 8 | 9 | //"encoding/hex" 10 | "encoding/json" 11 | //"errors" 12 | //"fmt" 13 | "net" 14 | "net/url" 15 | 16 | //"sort" 17 | //"time" 18 | 19 | "github.com/Arceliar/phony" 20 | //"github.com/RiV-chain/RiV-mesh/src/address" 21 | ) 22 | 23 | type SelfInfo struct { 24 | Key ed25519.PublicKey 25 | Root ed25519.PublicKey 26 | PrivateKey ed25519.PrivateKey 27 | Coords []uint64 28 | } 29 | 30 | type PeerInfo struct { 31 | Key ed25519.PublicKey 32 | Root ed25519.PublicKey 33 | Coords []uint64 34 | Port uint64 35 | Priority uint8 36 | Remote string 37 | RXBytes uint64 38 | TXBytes uint64 39 | Uptime time.Duration 40 | RemoteIp string 41 | } 42 | 43 | type DHTEntryInfo struct { 44 | Key ed25519.PublicKey 45 | Port uint64 46 | Rest uint64 47 | } 48 | 49 | type PathEntryInfo struct { 50 | Key ed25519.PublicKey 51 | Path []uint64 52 | } 53 | 54 | type SessionInfo struct { 55 | Key ed25519.PublicKey 56 | RXBytes uint64 57 | TXBytes uint64 58 | Uptime time.Duration 59 | } 60 | 61 | func (c *Core) GetSelf() SelfInfo { 62 | var self SelfInfo 63 | s := c.PacketConn.PacketConn.Debug.GetSelf() 64 | self.Key = s.Key 65 | self.PrivateKey = c.secret 66 | self.Root = s.Root 67 | self.Coords = s.Coords 68 | return self 69 | } 70 | 71 | func (c *Core) GetPeers() []PeerInfo { 72 | var peers []PeerInfo 73 | names := make(map[net.Conn]string) 74 | ips := make(map[net.Conn]string) 75 | phony.Block(&c.links, func() { 76 | for _, info := range c.links._links { 77 | if info == nil { 78 | continue 79 | } 80 | names[info.conn] = info.lname 81 | ips[info.conn] = info.info.remote 82 | } 83 | }) 84 | ps := c.PacketConn.PacketConn.Debug.GetPeers() 85 | for _, p := range ps { 86 | var info PeerInfo 87 | info.Key = p.Key 88 | info.Root = p.Root 89 | info.Coords = p.Coords 90 | info.Port = p.Port 91 | info.Priority = p.Priority 92 | info.Remote = p.Conn.RemoteAddr().String() 93 | if name := names[p.Conn]; name != "" { 94 | info.Remote = name 95 | } 96 | if info.RemoteIp = ips[p.Conn]; info.RemoteIp != "" { 97 | //Cut port 98 | if host, _, err := net.SplitHostPort(info.RemoteIp); err == nil { 99 | info.RemoteIp = host 100 | } 101 | } 102 | if linkconn, ok := p.Conn.(*linkConn); ok { 103 | info.RXBytes = atomic.LoadUint64(&linkconn.rx) 104 | info.TXBytes = atomic.LoadUint64(&linkconn.tx) 105 | info.Uptime = time.Since(linkconn.up) 106 | } 107 | peers = append(peers, info) 108 | } 109 | return peers 110 | } 111 | 112 | func (c *Core) GetDHT() []DHTEntryInfo { 113 | var dhts []DHTEntryInfo 114 | ds := c.PacketConn.PacketConn.Debug.GetDHT() 115 | for _, d := range ds { 116 | var info DHTEntryInfo 117 | info.Key = d.Key 118 | info.Port = d.Port 119 | info.Rest = d.Rest 120 | dhts = append(dhts, info) 121 | } 122 | return dhts 123 | } 124 | 125 | func (c *Core) GetPaths() []PathEntryInfo { 126 | var paths []PathEntryInfo 127 | ps := c.PacketConn.PacketConn.Debug.GetPaths() 128 | for _, p := range ps { 129 | var info PathEntryInfo 130 | info.Key = p.Key 131 | info.Path = p.Path 132 | paths = append(paths, info) 133 | } 134 | return paths 135 | } 136 | 137 | func (c *Core) GetSessions() []SessionInfo { 138 | var sessions []SessionInfo 139 | ss := c.PacketConn.Debug.GetSessions() 140 | for _, s := range ss { 141 | var info SessionInfo 142 | info.Key = s.Key 143 | info.RXBytes = s.RX 144 | info.TXBytes = s.TX 145 | info.Uptime = s.Uptime 146 | sessions = append(sessions, info) 147 | } 148 | return sessions 149 | } 150 | 151 | // Listen starts a new listener (either TCP or TLS). The input should be a url.URL 152 | // parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a 153 | // link-local address, the interface should be provided as the second argument. 154 | func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) { 155 | switch u.Scheme { 156 | case "tcp": 157 | return c.links.tcp.listen(u, sintf) 158 | case "tls": 159 | return c.links.tls.listen(u, sintf) 160 | case "unix": 161 | return c.links.unix.listen(u, sintf) 162 | default: 163 | return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme) 164 | } 165 | } 166 | 167 | // Address gets the IPv6 address of the Mesh node. This is always a /128 168 | // address. The IPv6 address is only relevant when the node is operating as an 169 | // IP router and often is meaningless when embedded into an application, unless 170 | // that application also implements either VPN functionality or deals with IP 171 | // packets specifically. 172 | func (c *Core) Address() net.IP { 173 | addr := net.IP(c.AddrForKey(c.public)[:]) 174 | return addr 175 | } 176 | 177 | // Subnet gets the routed IPv6 subnet of the Mesh node. This is always a 178 | // /64 subnet. The IPv6 subnet is only relevant when the node is operating as an 179 | // IP router and often is meaningless when embedded into an application, unless 180 | // that application also implements either VPN functionality or deals with IP 181 | // packets specifically. 182 | func (c *Core) Subnet() net.IPNet { 183 | subnet := c.SubnetForKey(c.public)[:] 184 | subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) 185 | return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} 186 | } 187 | 188 | // SetLogger sets the output logger of the Mesh node after startup. This 189 | // may be useful if you want to redirect the output later. Note that this 190 | // expects a Logger from the github.com/gologme/log package and not from Go's 191 | // built-in log package. 192 | func (c *Core) SetLogger(log Logger) { 193 | c.log = log 194 | } 195 | 196 | // AddPeer adds a peer. This should be specified in the peer URI format, e.g.: 197 | // tcp://a.b.c.d:e 198 | // socks://a.b.c.d:e/f.g.h.i:j 199 | // This adds the peer to the peer list, so that they will be called again if the 200 | // connection drops. 201 | 202 | func (c *Core) AddPeer(uri string, sourceInterface string) error { 203 | var known bool 204 | phony.Block(c, func() { 205 | _, known = c.config._peers[Peer{uri, sourceInterface}] 206 | }) 207 | if known { 208 | return fmt.Errorf("peer already configured") 209 | } 210 | u, err := url.Parse(uri) 211 | if err != nil { 212 | return err 213 | } 214 | info, err := c.links.call(u, sourceInterface, nil) 215 | if err != nil { 216 | return err 217 | } 218 | phony.Block(c, func() { 219 | c.config._peers[Peer{uri, sourceInterface}] = &info 220 | }) 221 | return nil 222 | } 223 | 224 | func (c *Core) RemovePeer(uri string, sourceInterface string) error { 225 | var err error 226 | phony.Block(c, func() { 227 | peer := Peer{uri, sourceInterface} 228 | linkInfo, ok := c.config._peers[peer] 229 | if !ok { 230 | err = fmt.Errorf("peer not configured") 231 | return 232 | } 233 | if ok && linkInfo != nil { 234 | c.links.Act(nil, func() { 235 | if link := c.links._links[*linkInfo]; link != nil { 236 | _ = link.close() 237 | } 238 | }) 239 | } 240 | delete(c.config._peers, peer) 241 | }) 242 | return err 243 | } 244 | 245 | func (c *Core) RemovePeers() error { 246 | phony.Block(c, func() { 247 | for peer, linkInfo := range c.config._peers { 248 | if linkInfo != nil { 249 | c.links.Act(nil, func() { 250 | if link := c.links._links[*linkInfo]; link != nil { 251 | _ = link.close() 252 | } 253 | }) 254 | } 255 | delete(c.config._peers, peer) 256 | } 257 | }) 258 | return nil 259 | } 260 | 261 | // CallPeer calls a peer once. This should be specified in the peer URI format, 262 | // e.g.: 263 | // 264 | // tcp://a.b.c.d:e 265 | // socks://a.b.c.d:e/f.g.h.i:j 266 | // 267 | // This does not add the peer to the peer list, so if the connection drops, the 268 | // peer will not be called again automatically. 269 | func (c *Core) CallPeer(u *url.URL, sintf string) error { 270 | _, err := c.links.call(u, sintf, nil) 271 | return err 272 | } 273 | 274 | func (c *Core) PublicKey() ed25519.PublicKey { 275 | return c.public 276 | } 277 | 278 | // Hack to get the admin stuff working, TODO something cleaner 279 | 280 | type AddHandler interface { 281 | AddHandler(name, desc string, args []string, handlerfunc AddHandlerFunc) error 282 | } 283 | 284 | type AddHandlerFunc func(json.RawMessage) (interface{}, error) 285 | 286 | // SetAdmin must be called after Init and before Start. 287 | // It sets the admin handler for NodeInfo and the Debug admin functions. 288 | func (c *Core) SetAdmin(a AddHandler) error { 289 | if err := a.AddHandler( 290 | "getNodeInfo", "Request nodeinfo from a remote node by its public key", []string{"key"}, 291 | c.proto.nodeinfo.nodeInfoAdminHandler, 292 | ); err != nil { 293 | return err 294 | } 295 | if err := a.AddHandler( 296 | "debug_remoteGetSelf", "Debug use only", []string{"key"}, 297 | c.proto.getSelfHandler, 298 | ); err != nil { 299 | return err 300 | } 301 | if err := a.AddHandler( 302 | "debug_remoteGetPeers", "Debug use only", []string{"key"}, 303 | c.proto.getPeersHandler, 304 | ); err != nil { 305 | return err 306 | } 307 | if err := a.AddHandler( 308 | "debug_remoteGetDHT", "Debug use only", []string{"key"}, 309 | c.proto.getDHTHandler, 310 | ); err != nil { 311 | return err 312 | } 313 | return nil 314 | } 315 | 316 | func applyAdminCall(handlerfunc AddHandlerFunc, key string) (result map[string]any, err error) { 317 | var in []byte 318 | if in, err = json.Marshal(map[string]any{"key": key}); err != nil { 319 | return 320 | } 321 | var out1 any 322 | if out1, err = handlerfunc(in); err != nil { 323 | return 324 | } 325 | var out2 []byte 326 | if out2, err = json.Marshal(out1); err != nil { 327 | return 328 | } 329 | err = json.Unmarshal(out2, &result) 330 | return 331 | } 332 | 333 | func (c *Core) GetNodeInfo(key string) (result map[string]any, err error) { 334 | return applyAdminCall(c.proto.nodeinfo.nodeInfoAdminHandler, key) 335 | } 336 | 337 | func (c *Core) RemoteGetSelf(key string) (map[string]any, error) { 338 | return applyAdminCall(c.proto.getSelfHandler, key) 339 | } 340 | 341 | func (c *Core) RemoteGetPeers(key string) (map[string]any, error) { 342 | return applyAdminCall(c.proto.getPeersHandler, key) 343 | } 344 | 345 | func (c *Core) RemoteGetDHT(key string) (map[string]any, error) { 346 | return applyAdminCall(c.proto.getDHTHandler, key) 347 | } 348 | -------------------------------------------------------------------------------- /src/core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "crypto/ed25519" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/url" 11 | "time" 12 | 13 | iwe "github.com/Arceliar/ironwood/encrypted" 14 | iwt "github.com/Arceliar/ironwood/types" 15 | "github.com/Arceliar/phony" 16 | "github.com/gologme/log" 17 | signals "github.com/vorot93/golang-signals" 18 | 19 | "github.com/RiV-chain/RiV-mesh/src/version" 20 | ) 21 | 22 | // The Core object represents the Mesh node. You should create a Core 23 | // object for each Mesh node you plan to run. 24 | type Core struct { 25 | // This is the main data structure that holds everything else for a node 26 | // We're going to keep our own copy of the provided config - that way we can 27 | // guarantee that it will be covered by the mutex 28 | phony.Inbox 29 | *iwe.PacketConn 30 | ctx context.Context 31 | cancel context.CancelFunc 32 | secret ed25519.PrivateKey 33 | public ed25519.PublicKey 34 | links links 35 | proto protoHandler 36 | log Logger 37 | addPeerTimer *time.Timer 38 | PeersChangedSignal signals.Signal 39 | config struct { 40 | _peers map[Peer]*linkInfo // configurable after startup 41 | _listeners map[ListenAddress]struct{} // configurable after startup 42 | nodeinfo NodeInfo // configurable after startup 43 | nodeinfoPrivacy NodeInfoPrivacy // immutable after startup 44 | _allowedPublicKeys map[[32]byte]struct{} // configurable after startup 45 | networkdomain NetworkDomain // immutable after startup 46 | } 47 | } 48 | 49 | func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core, error) { 50 | c := &Core{ 51 | log: logger, 52 | } 53 | if name := version.BuildName(); name != "unknown" { 54 | c.log.Infoln("Build name:", name) 55 | } 56 | if version := version.BuildVersion(); version != "unknown" { 57 | c.log.Infoln("Build version:", version) 58 | } 59 | c.ctx, c.cancel = context.WithCancel(context.Background()) 60 | // Take a copy of the private key so that it is in our own memory space. 61 | if len(secret) != ed25519.PrivateKeySize { 62 | return nil, fmt.Errorf("private key is incorrect length") 63 | } 64 | c.secret = make(ed25519.PrivateKey, ed25519.PrivateKeySize) 65 | copy(c.secret, secret) 66 | c.public = secret.Public().(ed25519.PublicKey) 67 | var err error 68 | if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { 69 | return nil, fmt.Errorf("error creating encryption: %w", err) 70 | } 71 | c.config._peers = map[Peer]*linkInfo{} 72 | c.config._listeners = map[ListenAddress]struct{}{} 73 | c.config._allowedPublicKeys = map[[32]byte]struct{}{} 74 | for _, opt := range opts { 75 | c._applyOption(opt) 76 | } 77 | if c.log == nil { 78 | c.log = log.New(io.Discard, "", 0) 79 | } 80 | c.proto.init(c) 81 | if err := c.links.init(c); err != nil { 82 | return nil, fmt.Errorf("error initialising links: %w", err) 83 | } 84 | if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { 85 | return nil, fmt.Errorf("error setting node info: %w", err) 86 | } 87 | for listenaddr := range c.config._listeners { 88 | u, err := url.Parse(string(listenaddr)) 89 | if err != nil { 90 | c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr) 91 | continue 92 | } 93 | if _, err = c.links.listen(u, ""); err != nil { 94 | c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err) 95 | } 96 | } 97 | c.Act(nil, c._addPeerLoop) 98 | return c, nil 99 | } 100 | 101 | func (c *Core) SetThisNodeInfo(nodeinfo NodeInfo) error { 102 | if err := c.proto.nodeinfo.setNodeInfo(nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { 103 | return fmt.Errorf("error setting node info: %w", err) 104 | } 105 | return nil 106 | } 107 | 108 | func (c *Core) GetThisNodeInfo() json.RawMessage { 109 | return c.proto.nodeinfo._getNodeInfo() 110 | } 111 | 112 | // If any static peers were provided in the configuration above then we should 113 | // configure them. The loop ensures that disconnected peers will eventually 114 | // be reconnected with. 115 | func (c *Core) _addPeerLoop() { 116 | select { 117 | case <-c.ctx.Done(): 118 | return 119 | default: 120 | } 121 | // Add peers from the Peers section 122 | for peer := range c.config._peers { 123 | go func(peer string, intf string) { 124 | u, err := url.Parse(peer) 125 | if err != nil { 126 | c.log.Errorln("Failed to parse peer url:", peer, err) 127 | } 128 | if err := c.CallPeer(u, intf); err != nil { 129 | c.log.Errorln("Failed to add peer:", err) 130 | } 131 | }(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine? 132 | } 133 | 134 | c.addPeerTimer = time.AfterFunc(time.Minute, func() { 135 | c.Act(nil, c._addPeerLoop) 136 | }) 137 | } 138 | 139 | func (c *Core) RetryPeersNow() { 140 | if c.addPeerTimer != nil && !c.addPeerTimer.Stop() { 141 | <-c.addPeerTimer.C 142 | } 143 | c.Act(nil, c._addPeerLoop) 144 | } 145 | 146 | // Stop shuts down the Mesh node. 147 | func (c *Core) Stop() { 148 | phony.Block(c, func() { 149 | c.log.Infoln("Stopping...") 150 | _ = c._close() 151 | c.log.Infoln("Stopped") 152 | }) 153 | } 154 | 155 | // This function is unsafe and should only be ran by the core actor. 156 | func (c *Core) _close() error { 157 | c.cancel() 158 | c.links.shutdown() 159 | err := c.PacketConn.Close() 160 | if c.addPeerTimer != nil { 161 | c.addPeerTimer.Stop() 162 | c.addPeerTimer = nil 163 | } 164 | return err 165 | } 166 | 167 | func (c *Core) MTU() uint64 { 168 | const sessionTypeOverhead = 1 169 | return c.PacketConn.MTU() - sessionTypeOverhead 170 | } 171 | 172 | func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) { 173 | buf := make([]byte, c.PacketConn.MTU(), 65535) 174 | for { 175 | bs := buf 176 | n, from, err = c.PacketConn.ReadFrom(bs) 177 | if err != nil { 178 | return 0, from, err 179 | } 180 | if n == 0 { 181 | continue 182 | } 183 | switch bs[0] { 184 | case typeSessionTraffic: 185 | // This is what we want to handle here 186 | case typeSessionProto: 187 | var key keyArray 188 | copy(key[:], from.(iwt.Addr)) 189 | data := append([]byte(nil), bs[1:n]...) 190 | c.proto.handleProto(nil, key, data) 191 | continue 192 | default: 193 | continue 194 | } 195 | bs = bs[1:n] 196 | copy(p, bs) 197 | if len(p) < len(bs) { 198 | n = len(p) 199 | } else { 200 | n = len(bs) 201 | } 202 | return 203 | } 204 | } 205 | 206 | func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { 207 | buf := make([]byte, 0, 65535) 208 | buf = append(buf, typeSessionTraffic) 209 | buf = append(buf, p...) 210 | n, err = c.PacketConn.WriteTo(buf, addr) 211 | if n > 0 { 212 | n -= 1 213 | } 214 | return 215 | } 216 | 217 | type Logger interface { 218 | Printf(string, ...interface{}) 219 | Println(...interface{}) 220 | Infof(string, ...interface{}) 221 | Infoln(...interface{}) 222 | Warnf(string, ...interface{}) 223 | Warnln(...interface{}) 224 | Errorf(string, ...interface{}) 225 | Errorln(...interface{}) 226 | Debugf(string, ...interface{}) 227 | Debugln(...interface{}) 228 | Traceln(...interface{}) 229 | } 230 | -------------------------------------------------------------------------------- /src/core/core_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "math/rand" 7 | "net/url" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/gologme/log" 13 | ) 14 | 15 | // GetLoggerWithPrefix creates a new logger instance with prefix. 16 | // If verbose is set to true, three log levels are enabled: "info", "warn", "error". 17 | func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { 18 | l := log.New(os.Stderr, prefix, log.Flags()) 19 | if !verbose { 20 | return l 21 | } 22 | l.EnableLevel("info") 23 | l.EnableLevel("warn") 24 | l.EnableLevel("error") 25 | return l 26 | } 27 | 28 | // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. 29 | // Verbosity flag is passed to logger. 30 | func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { 31 | var err error 32 | var skA, skB ed25519.PrivateKey 33 | if _, skA, err = ed25519.GenerateKey(nil); err != nil { 34 | t.Fatal(err) 35 | } 36 | if _, skB, err = ed25519.GenerateKey(nil); err != nil { 37 | t.Fatal(err) 38 | } 39 | logger := GetLoggerWithPrefix("", false) 40 | if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil { 41 | t.Fatal(err) 42 | } 43 | if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | err = nodeB.CallPeer(u, "") 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | time.Sleep(100 * time.Millisecond) 57 | 58 | if l := len(nodeA.GetPeers()); l != 1 { 59 | t.Fatal("unexpected number of peers", l) 60 | } 61 | if l := len(nodeB.GetPeers()); l != 1 { 62 | t.Fatal("unexpected number of peers", l) 63 | } 64 | 65 | return nodeA, nodeB 66 | } 67 | 68 | // WaitConnected blocks until either nodes negotiated DHT or 5 seconds passed. 69 | func WaitConnected(nodeA, nodeB *Core) bool { 70 | // It may take up to 3 seconds, but let's wait 5. 71 | for i := 0; i < 50; i++ { 72 | time.Sleep(100 * time.Millisecond) 73 | if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 { 74 | return true 75 | } 76 | } 77 | return false 78 | } 79 | 80 | // CreateEchoListener creates a routine listening on nodeA. It expects repeats messages of length bufLen. 81 | // It returns a channel used to synchronize the routine with caller. 82 | func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan struct{} { 83 | // Start routine 84 | done := make(chan struct{}) 85 | go func() { 86 | buf := make([]byte, bufLen) 87 | res := make([]byte, bufLen) 88 | for i := 0; i < repeats; i++ { 89 | n, from, err := nodeA.ReadFrom(buf) 90 | if err != nil { 91 | t.Error(err) 92 | return 93 | } 94 | if n != bufLen { 95 | t.Error("missing data") 96 | return 97 | } 98 | copy(res, buf) 99 | copy(res[8:24], buf[24:40]) 100 | copy(res[24:40], buf[8:24]) 101 | _, err = nodeA.WriteTo(res, from) 102 | if err != nil { 103 | t.Error(err) 104 | } 105 | } 106 | done <- struct{}{} 107 | }() 108 | 109 | return done 110 | } 111 | 112 | // TestCore_Start_Connect checks if two nodes can connect together. 113 | func TestCore_Start_Connect(t *testing.T) { 114 | CreateAndConnectTwo(t, true) 115 | } 116 | 117 | // TestCore_Start_Transfer checks that messages can be passed between nodes (in both directions). 118 | func TestCore_Start_Transfer(t *testing.T) { 119 | nodeA, nodeB := CreateAndConnectTwo(t, true) 120 | defer nodeA.Stop() 121 | defer nodeB.Stop() 122 | 123 | msgLen := 1500 124 | done := CreateEchoListener(t, nodeA, msgLen, 1) 125 | 126 | if !WaitConnected(nodeA, nodeB) { 127 | t.Fatal("nodes did not connect") 128 | } 129 | 130 | // Send 131 | msg := make([]byte, msgLen) 132 | rand.Read(msg[40:]) 133 | msg[0] = 0x60 134 | copy(msg[8:24], nodeB.Address()) 135 | copy(msg[24:40], nodeA.Address()) 136 | _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | buf := make([]byte, msgLen) 141 | _, _, err = nodeB.ReadFrom(buf) 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | if !bytes.Equal(msg[40:], buf[40:]) { 146 | t.Fatal("expected echo") 147 | } 148 | <-done 149 | } 150 | 151 | // BenchmarkCore_Start_Transfer estimates the possible transfer between nodes (in MB/s). 152 | func BenchmarkCore_Start_Transfer(b *testing.B) { 153 | nodeA, nodeB := CreateAndConnectTwo(b, false) 154 | 155 | msgLen := 1500 // typical MTU 156 | done := CreateEchoListener(b, nodeA, msgLen, b.N) 157 | 158 | if !WaitConnected(nodeA, nodeB) { 159 | b.Fatal("nodes did not connect") 160 | } 161 | 162 | // Send 163 | msg := make([]byte, msgLen) 164 | rand.Read(msg[40:]) 165 | msg[0] = 0x60 166 | copy(msg[8:24], nodeB.Address()) 167 | copy(msg[24:40], nodeA.Address()) 168 | 169 | buf := make([]byte, msgLen) 170 | 171 | b.SetBytes(int64(msgLen)) 172 | b.ResetTimer() 173 | 174 | addr := nodeA.LocalAddr() 175 | for i := 0; i < b.N; i++ { 176 | _, err := nodeB.WriteTo(msg, addr) 177 | if err != nil { 178 | b.Fatal(err) 179 | } 180 | _, _, err = nodeB.ReadFrom(buf) 181 | if err != nil { 182 | b.Fatal(err) 183 | } 184 | } 185 | <-done 186 | } 187 | -------------------------------------------------------------------------------- /src/core/debug.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | // +build debug 3 | 4 | package core 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | _ "net/http/pprof" 10 | "os" 11 | "runtime" 12 | 13 | "github.com/gologme/log" 14 | ) 15 | 16 | // Start the profiler in debug builds, if the required environment variable is set. 17 | func init() { 18 | envVarName := "PPROFLISTEN" 19 | hostPort := os.Getenv(envVarName) 20 | switch { 21 | case hostPort == "": 22 | fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName) 23 | default: 24 | fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort) 25 | go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }() 26 | } 27 | } 28 | 29 | // Starts the function profiler. This is only supported when built with 30 | // '-tags build'. 31 | func StartProfiler(log *log.Logger) error { 32 | runtime.SetBlockProfileRate(1) 33 | go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /src/core/link.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/url" 11 | "strconv" 12 | "strings" 13 | 14 | "time" 15 | 16 | "sync/atomic" 17 | 18 | "github.com/Arceliar/phony" 19 | //"github.com/RiV-chain/RiV-mesh/src/address" 20 | //"github.com/Arceliar/phony" // TODO? use instead of mutexes 21 | ) 22 | 23 | // linkInfo is used as a map key 24 | type linkInfo struct { 25 | linkType string // Type of link, e.g. TCP, AWDL 26 | local string // Local name or address 27 | remote string // Remote name or address 28 | } 29 | 30 | type linkDial struct { 31 | url *url.URL 32 | sintf string 33 | } 34 | 35 | type link struct { 36 | lname string 37 | links *links 38 | conn *linkConn 39 | options linkOptions 40 | info linkInfo 41 | incoming bool 42 | force bool 43 | } 44 | 45 | type linkOptions struct { 46 | pinnedEd25519Keys map[keyArray]struct{} 47 | priority uint8 48 | } 49 | 50 | type Listener struct { 51 | net.Listener 52 | closed chan struct{} 53 | } 54 | 55 | func (l *Listener) Close() error { 56 | err := l.Listener.Close() 57 | <-l.closed 58 | return err 59 | } 60 | 61 | func (l *links) shutdown() { 62 | phony.Block(l.tcp, func() { 63 | for l := range l.tcp._listeners { 64 | _ = l.Close() 65 | } 66 | }) 67 | phony.Block(l.tls, func() { 68 | for l := range l.tls._listeners { 69 | _ = l.Close() 70 | } 71 | }) 72 | phony.Block(l.unix, func() { 73 | for l := range l.unix._listeners { 74 | _ = l.Close() 75 | } 76 | }) 77 | } 78 | 79 | func (l *links) isConnectedTo(info linkInfo) bool { 80 | var isConnected bool 81 | phony.Block(l, func() { 82 | _, isConnected = l._links[info] 83 | }) 84 | return isConnected 85 | } 86 | 87 | func (l *links) create(conn net.Conn, dial *linkDial, name string, info linkInfo, incoming, force bool, options linkOptions) error { 88 | intf := link{ 89 | conn: &linkConn{ 90 | Conn: conn, 91 | up: time.Now(), 92 | }, 93 | lname: name, 94 | links: l, 95 | options: options, 96 | info: info, 97 | incoming: incoming, 98 | force: force, 99 | } 100 | go func() { 101 | if err := intf.handler(dial); err != nil { 102 | l.core.log.Errorf("Link handler %s error (%s): %s", name, conn.RemoteAddr(), err) 103 | } 104 | }() 105 | return nil 106 | } 107 | 108 | func (intf *link) handler(dial *linkDial) error { 109 | defer intf.conn.Close() // nolint:errcheck 110 | 111 | // Don't connect to this link more than once. 112 | if intf.links.isConnectedTo(intf.info) { 113 | return nil 114 | } 115 | 116 | // Mark the connection as in progress. 117 | phony.Block(intf.links, func() { 118 | intf.links._links[intf.info] = nil 119 | }) 120 | 121 | // When we're done, clean up the connection entry. 122 | defer phony.Block(intf.links, func() { 123 | delete(intf.links._links, intf.info) 124 | }) 125 | 126 | meta := version_getBaseMetadata() 127 | meta.key = intf.links.core.public 128 | metaBytes := meta.encode() 129 | if err := intf.conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil { 130 | return fmt.Errorf("failed to set handshake deadline: %w", err) 131 | } 132 | n, err := intf.conn.Write(metaBytes) 133 | switch { 134 | case err != nil: 135 | return fmt.Errorf("write handshake: %w", err) 136 | case err == nil && n != len(metaBytes): 137 | return fmt.Errorf("incomplete handshake send") 138 | } 139 | if _, err = io.ReadFull(intf.conn, metaBytes); err != nil { 140 | return fmt.Errorf("read handshake: %w", err) 141 | } 142 | if err = intf.conn.SetDeadline(time.Time{}); err != nil { 143 | return fmt.Errorf("failed to clear handshake deadline: %w", err) 144 | } 145 | meta = version_metadata{} 146 | base := version_getBaseMetadata() 147 | if !meta.decode(metaBytes) { 148 | return errors.New("failed to decode metadata") 149 | } 150 | if !meta.check() { 151 | var connectError string 152 | if intf.incoming { 153 | connectError = "Rejected incoming connection" 154 | } else { 155 | connectError = "Failed to connect" 156 | } 157 | intf.links.core.log.Debugf("%s: %s is incompatible version (local %s, remote %s)", 158 | connectError, 159 | intf.lname, 160 | fmt.Sprintf("%d.%d", base.ver, base.minorVer), 161 | fmt.Sprintf("%d.%d", meta.ver, meta.minorVer), 162 | ) 163 | return errors.New("remote node is incompatible version") 164 | } 165 | // Check if the remote side matches the keys we expected. This is a bit of a weak 166 | // check - in future versions we really should check a signature or something like that. 167 | if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { 168 | var key keyArray 169 | copy(key[:], meta.key) 170 | if _, allowed := pinned[key]; !allowed { 171 | return fmt.Errorf("node public key that does not match pinned keys") 172 | } 173 | } 174 | // Check if we're authorized to connect to this key / IP 175 | allowed := intf.links.core.config._allowedPublicKeys 176 | isallowed := len(allowed) == 0 177 | for k := range allowed { 178 | if bytes.Equal(k[:], meta.key) { 179 | isallowed = true 180 | break 181 | } 182 | } 183 | if intf.incoming && !intf.force && !isallowed { 184 | _ = intf.close() 185 | return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.key)) 186 | } 187 | 188 | phony.Block(intf.links, func() { 189 | intf.links._links[intf.info] = intf 190 | }) 191 | 192 | dir := "outbound" 193 | if intf.incoming { 194 | dir = "inbound" 195 | } 196 | remoteAddr := net.IP(intf.links.core.AddrForKey(meta.key)[:]).String() 197 | remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote) 198 | localStr := intf.conn.LocalAddr() 199 | intf.links.core.log.Infof("Connected %s %s: %s, source %s", 200 | dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) 201 | 202 | time.AfterFunc(time.Millisecond*500, func() { 203 | intf.links.core.PeersChangedSignal.Emit(nil) 204 | }) 205 | err = intf.links.core.HandleConn(meta.key, intf.conn, intf.options.priority) 206 | switch err { 207 | case io.EOF, net.ErrClosed, nil: 208 | intf.links.core.log.Infof("Disconnected %s %s: %s, source %s", 209 | dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) 210 | default: 211 | intf.links.core.log.Infof("Disconnected %s %s: %s, source %s; error: %s", 212 | dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) 213 | } 214 | intf.links.core.PeersChangedSignal.Emit(nil) 215 | 216 | if !intf.incoming && dial != nil { 217 | // The connection was one that we dialled, so wait a second and try to 218 | // dial it again. 219 | var retry func(attempt int) 220 | retry = func(attempt int) { 221 | // intf.links.core.log.Infof("Retrying %s (attempt %d of 5)...", dial.url.String(), attempt) 222 | errch := make(chan error, 1) 223 | if _, err := intf.links.call(dial.url, dial.sintf, errch); err != nil { 224 | return 225 | } 226 | if err := <-errch; err != nil { 227 | if attempt < 3 { 228 | time.AfterFunc(time.Second, func() { 229 | retry(attempt + 1) 230 | }) 231 | } 232 | } 233 | } 234 | time.AfterFunc(time.Second, func() { 235 | retry(1) 236 | }) 237 | } 238 | 239 | return nil 240 | } 241 | 242 | func (intf *link) close() error { 243 | return intf.conn.Close() 244 | } 245 | 246 | func linkInfoFor(linkType, sintf, remote string) linkInfo { 247 | return linkInfo{ 248 | linkType: linkType, 249 | local: sintf, 250 | remote: remote, 251 | } 252 | } 253 | 254 | type linkConn struct { 255 | // tx and rx are at the beginning of the struct to ensure 64-bit alignment 256 | // on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG 257 | rx uint64 258 | tx uint64 259 | up time.Time 260 | net.Conn 261 | } 262 | 263 | func (c *linkConn) Read(p []byte) (n int, err error) { 264 | n, err = c.Conn.Read(p) 265 | atomic.AddUint64(&c.rx, uint64(n)) 266 | return 267 | } 268 | 269 | func (c *linkConn) Write(p []byte) (n int, err error) { 270 | n, err = c.Conn.Write(p) 271 | atomic.AddUint64(&c.tx, uint64(n)) 272 | return 273 | } 274 | 275 | func linkOptionsForListener(u *url.URL) (l linkOptions) { 276 | if p := u.Query().Get("priority"); p != "" { 277 | if pi, err := strconv.ParseUint(p, 10, 8); err == nil { 278 | l.priority = uint8(pi) 279 | } 280 | } 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /src/core/link_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package core 5 | 6 | import ( 7 | "encoding/hex" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | "net/url" 13 | "strconv" 14 | 15 | "github.com/Arceliar/phony" 16 | //"github.com/Arceliar/phony" // TODO? use instead of mutexes 17 | ) 18 | 19 | type links struct { 20 | phony.Inbox 21 | core *Core 22 | tcp *linkTCP // TCP interface support 23 | tls *linkTLS // TLS interface support 24 | unix *linkUNIX // UNIX interface support 25 | socks *linkSOCKS // SOCKS interface support 26 | sctp *linkSCTP // SCTP interface support 27 | mpath *linkMPATH // Multipath interface support 28 | _links map[linkInfo]*link // *link is nil if connection in progress 29 | // TODO timeout (to remove from switch), read from config.ReadTimeout 30 | } 31 | 32 | func (l *links) init(c *Core) error { 33 | l.core = c 34 | l.tcp = l.newLinkTCP() 35 | l.tls = l.newLinkTLS(l.tcp) 36 | l.unix = l.newLinkUNIX() 37 | l.socks = l.newLinkSOCKS() 38 | l.sctp = l.newLinkSCTP() 39 | l.mpath = l.newLinkMPATH() 40 | 41 | l._links = make(map[linkInfo]*link) 42 | 43 | var listeners []ListenAddress 44 | phony.Block(c, func() { 45 | listeners = make([]ListenAddress, 0, len(c.config._listeners)) 46 | for listener := range c.config._listeners { 47 | listeners = append(listeners, listener) 48 | } 49 | }) 50 | 51 | return nil 52 | } 53 | 54 | func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInfo, err error) { 55 | info = linkInfoFor(u.Scheme, sintf, u.Host) 56 | if l.isConnectedTo(info) { 57 | if errch != nil { 58 | close(errch) // already connected, no error 59 | } 60 | return info, nil 61 | } 62 | options := linkOptions{ 63 | pinnedEd25519Keys: map[keyArray]struct{}{}, 64 | } 65 | for _, pubkey := range u.Query()["key"] { 66 | sigPub, err := hex.DecodeString(pubkey) 67 | if err != nil { 68 | if errch != nil { 69 | close(errch) 70 | } 71 | return info, fmt.Errorf("pinned key contains invalid hex characters") 72 | } 73 | var sigPubKey keyArray 74 | copy(sigPubKey[:], sigPub) 75 | options.pinnedEd25519Keys[sigPubKey] = struct{}{} 76 | } 77 | if p := u.Query().Get("priority"); p != "" { 78 | pi, err := strconv.ParseUint(p, 10, 8) 79 | if err != nil { 80 | if errch != nil { 81 | close(errch) 82 | } 83 | return info, fmt.Errorf("priority invalid: %w", err) 84 | } 85 | options.priority = uint8(pi) 86 | } 87 | switch info.linkType { 88 | case "tcp": 89 | go func() { 90 | if errch != nil { 91 | defer close(errch) 92 | } 93 | if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF { 94 | l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err) 95 | if errch != nil { 96 | errch <- err 97 | } 98 | } 99 | }() 100 | 101 | case "socks": 102 | go func() { 103 | if errch != nil { 104 | defer close(errch) 105 | } 106 | if err := l.socks.dial(u, options); err != nil && err != io.EOF { 107 | l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err) 108 | if errch != nil { 109 | errch <- err 110 | } 111 | } 112 | }() 113 | 114 | case "tls": 115 | // SNI headers must contain hostnames and not IP addresses, so we must make sure 116 | // that we do not populate the SNI with an IP literal. We do this by splitting 117 | // the host-port combo from the query option and then seeing if it parses to an 118 | // IP address successfully or not. 119 | var tlsSNI string 120 | if sni := u.Query().Get("sni"); sni != "" { 121 | if net.ParseIP(sni) == nil { 122 | tlsSNI = sni 123 | } 124 | } 125 | // If the SNI is not configured still because the above failed then we'll try 126 | // again but this time we'll use the host part of the peering URI instead. 127 | if tlsSNI == "" { 128 | if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { 129 | tlsSNI = host 130 | } 131 | } 132 | go func() { 133 | if errch != nil { 134 | defer close(errch) 135 | } 136 | if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF { 137 | l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err) 138 | if errch != nil { 139 | errch <- err 140 | } 141 | } 142 | }() 143 | 144 | case "unix": 145 | go func() { 146 | if errch != nil { 147 | defer close(errch) 148 | } 149 | if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF { 150 | l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err) 151 | if errch != nil { 152 | errch <- err 153 | } 154 | } 155 | }() 156 | case "sctp": 157 | go func() { 158 | if errch != nil { 159 | defer close(errch) 160 | } 161 | if err := l.sctp.dial(u, options, sintf); err != nil && err != io.EOF { 162 | l.core.log.Warnf("Failed to dial SCTP %s: %s\n", u.Host, err) 163 | if errch != nil { 164 | errch <- err 165 | } 166 | } 167 | }() 168 | case "mpath": 169 | go func() { 170 | if errch != nil { 171 | defer close(errch) 172 | } 173 | if err := l.mpath.dial(u, options, sintf); err != nil && err != io.EOF { 174 | l.core.log.Warnf("Failed to dial MPATH %s: %s\n", u.Host, err) 175 | if errch != nil { 176 | errch <- err 177 | } 178 | } 179 | }() 180 | default: 181 | if errch != nil { 182 | close(errch) 183 | } 184 | return info, errors.New("unknown call scheme: " + u.Scheme) 185 | } 186 | return info, nil 187 | } 188 | 189 | func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { 190 | var listener *Listener 191 | var err error 192 | switch u.Scheme { 193 | case "tcp": 194 | listener, err = l.tcp.listen(u, sintf) 195 | case "tls": 196 | listener, err = l.tls.listen(u, sintf) 197 | case "unix": 198 | listener, err = l.unix.listen(u, sintf) 199 | case "sctp": 200 | listener, err = l.sctp.listen(u, sintf) 201 | case "mpath": 202 | listener, err = l.mpath.listen(u, sintf) 203 | default: 204 | return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme) 205 | } 206 | return listener, err 207 | } 208 | -------------------------------------------------------------------------------- /src/core/link_mpath.go: -------------------------------------------------------------------------------- 1 | //go:build !android 2 | // +build !android 3 | 4 | package core 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "net/url" 11 | "strings" 12 | 13 | "github.com/getlantern/multipath" 14 | 15 | "github.com/Arceliar/phony" 16 | ) 17 | 18 | type linkMPATH struct { 19 | phony.Inbox 20 | *links 21 | listener *net.ListenConfig 22 | _listeners map[*Listener]context.CancelFunc 23 | } 24 | 25 | func (l *links) newLinkMPATH() *linkMPATH { 26 | lt := &linkMPATH{ 27 | links: l, 28 | listener: &net.ListenConfig{ 29 | KeepAlive: -1, 30 | }, 31 | _listeners: map[*Listener]context.CancelFunc{}, 32 | } 33 | lt.listener.Control = lt.tcpContext 34 | return lt 35 | } 36 | 37 | func (l *linkMPATH) dial(url *url.URL, options linkOptions, sintf string) error { 38 | info := linkInfoFor("mpath", sintf, strings.SplitN(url.Host, "%", 2)[0]) 39 | conn, err := l.connFor(url, sintf) 40 | if err != nil { 41 | return err 42 | } 43 | name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") 44 | dial := &linkDial{ 45 | url: url, 46 | sintf: sintf, 47 | } 48 | return l.handler(dial, name, info, conn, options, false, false) 49 | } 50 | 51 | func (l *linkMPATH) listen(url *url.URL, sintf string) (*Listener, error) { 52 | /* 53 | hostport := url.Host 54 | if sintf != "" { 55 | if host, port, err := net.SplitHostPort(hostport); err == nil { 56 | hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) 57 | } 58 | } 59 | */ 60 | _, cancel := context.WithCancel(l.core.ctx) 61 | listener, err := l.listenFor(url, sintf) 62 | if err != nil { 63 | cancel() 64 | return nil, err 65 | } 66 | entry := &Listener{ 67 | Listener: listener, 68 | closed: make(chan struct{}), 69 | } 70 | phony.Block(l, func() { 71 | l._listeners[entry] = cancel 72 | }) 73 | l.core.log.Printf("Multipath listener started on %s", listener.Addr()) 74 | go func() { 75 | defer phony.Block(l, func() { 76 | delete(l._listeners, entry) 77 | }) 78 | for { 79 | conn, err := listener.Accept() 80 | if err != nil { 81 | cancel() 82 | break 83 | } 84 | raddr := conn.RemoteAddr().(*net.TCPAddr) 85 | name := fmt.Sprintf("mpath://%s", raddr) 86 | info := linkInfoFor("mpath", sintf, strings.SplitN(raddr.IP.String(), "%", 2)[0]) 87 | if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { 88 | l.core.log.Errorln("Failed to create inbound link:", err) 89 | } 90 | } 91 | _ = listener.Close() 92 | close(entry.closed) 93 | l.core.log.Printf("Multipath listener stopped on %s", listener.Addr()) 94 | }() 95 | return entry, nil 96 | } 97 | 98 | func (l *linkMPATH) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { 99 | return l.links.create( 100 | conn, // connection 101 | dial, // connection URL 102 | name, // connection name 103 | info, // connection info 104 | incoming, // not incoming 105 | force, // not forced 106 | options, // connection options 107 | ) 108 | } 109 | 110 | // Returns the address of the listener. 111 | // 112 | //nolint:unused 113 | func (l *linkMPATH) getAddr() *net.TCPAddr { 114 | // TODO: Fix this, because this will currently only give a single address 115 | // to multicast.go, which obviously is not great, but right now multicast.go 116 | // doesn't have the ability to send more than one address in a packet either 117 | var addr *net.TCPAddr 118 | phony.Block(l, func() { 119 | for listener := range l._listeners { 120 | addr = listener.Addr().(*net.TCPAddr) 121 | } 122 | }) 123 | return addr 124 | } 125 | 126 | func (l *linkMPATH) connFor(url *url.URL, sinterfaces string) (net.Conn, error) { 127 | //Peer url has following format: mpath://host-1:port-1/host-2:port-2.../host-n:port-n 128 | hosts := strings.Split(url.String(), "/")[2:] 129 | remoteTargets := make([]net.Addr, 0) 130 | for _, host := range hosts { 131 | dst, err := net.ResolveTCPAddr("tcp", host) 132 | info := linkInfoFor("tcp", sinterfaces, dst.String()) 133 | if l.links.isConnectedTo(info) { 134 | continue 135 | } 136 | if err != nil { 137 | l.core.log.Errorln("could not resolve host ", dst.String()) 138 | continue 139 | } 140 | if dst.IP.IsLinkLocalUnicast() { 141 | dst.Zone = sinterfaces 142 | if dst.Zone == "" { 143 | l.core.log.Errorln("link-local address requires a zone in ", dst.String()) 144 | continue 145 | } 146 | } 147 | remoteTargets = append(remoteTargets, dst) 148 | } 149 | 150 | if len(remoteTargets) == 0 { 151 | return nil, fmt.Errorf("no valid target hosts given") 152 | } 153 | 154 | dialers := make([]multipath.Dialer, 0) 155 | trackers := make([]multipath.StatsTracker, 0) 156 | if sinterfaces != "" { 157 | sintfarray := strings.Split(sinterfaces, ",") 158 | for _, dst := range remoteTargets { 159 | for _, sintf := range sintfarray { 160 | ief, err := net.InterfaceByName(sintf) 161 | if err != nil { 162 | l.core.log.Errorln("interface %s not found", sintf) 163 | continue 164 | } 165 | if ief.Flags&net.FlagUp == 0 { 166 | l.core.log.Errorln("interface %s is not up", sintf) 167 | continue 168 | } 169 | addrs, err := ief.Addrs() 170 | if err != nil { 171 | l.core.log.Errorln("interface %s addresses not available: %w", sintf, err) 172 | continue 173 | } 174 | dstIp := dst.(*net.TCPAddr).IP 175 | for addrindex, addr := range addrs { 176 | src, _, err := net.ParseCIDR(addr.String()) 177 | if err != nil { 178 | continue 179 | } 180 | if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { 181 | continue 182 | } 183 | bothglobal := src.IsGlobalUnicast() == dstIp.IsGlobalUnicast() 184 | bothlinklocal := src.IsLinkLocalUnicast() == dstIp.IsLinkLocalUnicast() 185 | if !bothglobal && !bothlinklocal { 186 | continue 187 | } 188 | if (src.To4() != nil) != (dstIp.To4() != nil) { 189 | continue 190 | } 191 | if bothglobal || bothlinklocal || addrindex == len(addrs)-1 { 192 | td := newOutboundDialer(src, dst) 193 | dialers = append(dialers, td) 194 | trackers = append(trackers, multipath.NullTracker{}) 195 | l.core.log.Printf("added outbound dialer for %s->%s", src.String(), dst.String()) 196 | break 197 | } 198 | } 199 | } 200 | } 201 | } else { 202 | star := net.ParseIP("0.0.0.0") 203 | for _, dst := range remoteTargets { 204 | td := newOutboundDialer(star, dst) 205 | dialers = append(dialers, td) 206 | trackers = append(trackers, multipath.NullTracker{}) 207 | l.core.log.Printf("added outbound dialer for %s", dst.String()) 208 | } 209 | } 210 | if len(dialers) == 0 { 211 | return nil, fmt.Errorf("no suitable source address found on interface %q", sinterfaces) 212 | } 213 | dialer := multipath.NewDialer("mpath", dialers) 214 | //conn, err := dialer.DialContext(l.core.ctx, "tcp", remoteTargets[0].String()) 215 | conn, err := dialer.DialContext(l.core.ctx) 216 | if err != nil { 217 | return nil, err 218 | } 219 | return conn, nil 220 | } 221 | 222 | func (l *linkMPATH) listenFor(url *url.URL, sintf string) (net.Listener, error) { 223 | //Public node url has following format: mpath://ip-1:port-1/ip-2:port-2.../ip-n:port-n 224 | hosts := strings.Split(url.String(), "/")[2:] 225 | localTargets := make([]string, 0) 226 | for _, host := range hosts { 227 | dst, err := net.ResolveTCPAddr("tcp", host) 228 | if err != nil { 229 | l.core.log.Errorln("could not resolve host ", dst.String()) 230 | continue 231 | } 232 | if dst.IP.IsLinkLocalUnicast() { 233 | dst.Zone = sintf 234 | if dst.Zone == "" { 235 | l.core.log.Errorln("link-local address requires a zone in ", dst.String()) 236 | continue 237 | } 238 | } 239 | localTargets = append(localTargets, host) 240 | } 241 | 242 | if len(localTargets) == 0 { 243 | return nil, fmt.Errorf("no valid target hosts given") 244 | } 245 | 246 | listeners := make([]net.Listener, 0) 247 | trackers := make([]multipath.StatsTracker, 0) 248 | for _, lT := range localTargets { 249 | l, err := l.listener.Listen(l.core.ctx, "tcp", lT) 250 | if err != nil { 251 | continue 252 | } 253 | listeners = append(listeners, l) 254 | trackers = append(trackers, multipath.NullTracker{}) 255 | } 256 | listener := multipath.NewListener(listeners, trackers) 257 | 258 | return listener, nil 259 | } 260 | 261 | type targetedDailer struct { 262 | localDialer net.Dialer 263 | remoteAddr net.Addr 264 | } 265 | 266 | func newOutboundDialer(inputLocalAddr net.IP, inputRemoteAddr net.Addr) *targetedDailer { 267 | td := &targetedDailer{ 268 | localDialer: net.Dialer{ 269 | LocalAddr: &net.TCPAddr{ 270 | IP: inputLocalAddr, 271 | Port: 0, 272 | }, 273 | }, 274 | remoteAddr: inputRemoteAddr, 275 | } 276 | return td 277 | } 278 | 279 | func (td *targetedDailer) DialContext(ctx context.Context) (net.Conn, error) { 280 | conn, err := td.localDialer.DialContext(ctx, "tcp", td.remoteAddr.String()) 281 | if err != nil { 282 | //l.core.log.Errorln("failed to dial to %v: %v", td.remoteAddr.String(), err) 283 | return nil, err 284 | } 285 | //l.core.log.Printf("Dialed to %v->%v", conn.LocalAddr(), td.remoteAddr.String()) 286 | 287 | return conn, err 288 | } 289 | 290 | func (td *targetedDailer) Label() string { 291 | return fmt.Sprintf("%s|%s", td.localDialer.LocalAddr, td.remoteAddr) 292 | } 293 | -------------------------------------------------------------------------------- /src/core/link_mpath_android.go: -------------------------------------------------------------------------------- 1 | //go:build android 2 | // +build android 3 | 4 | package core 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "github.com/getlantern/multipath" 10 | "net" 11 | "net/netip" 12 | "net/url" 13 | "strings" 14 | 15 | "github.com/Arceliar/phony" 16 | ) 17 | 18 | type linkMPATH struct { 19 | phony.Inbox 20 | *links 21 | listener *net.ListenConfig 22 | _listeners map[*Listener]context.CancelFunc 23 | } 24 | 25 | func (l *links) newLinkMPATH() *linkMPATH { 26 | lt := &linkMPATH{ 27 | links: l, 28 | listener: &net.ListenConfig{ 29 | KeepAlive: -1, 30 | }, 31 | _listeners: map[*Listener]context.CancelFunc{}, 32 | } 33 | lt.listener.Control = lt.tcpContext 34 | return lt 35 | } 36 | 37 | func (l *linkMPATH) dial(url *url.URL, options linkOptions, sintf string) error { 38 | info := linkInfoFor("mpath", sintf, strings.SplitN(url.Host, "%", 2)[0]) 39 | if l.links.isConnectedTo(info) { 40 | return fmt.Errorf("duplicate connection attempt") 41 | } 42 | conn, err := l.connFor(url, sintf) 43 | if err != nil { 44 | return err 45 | } 46 | dial := &linkDial{ 47 | url: url, 48 | sintf: sintf, 49 | } 50 | return l.handler(dial, url.String(), info, conn, options, false, false) 51 | } 52 | 53 | func (l *linkMPATH) listen(url *url.URL, sintf string) (*Listener, error) { 54 | hostport := url.Host 55 | if sintf != "" { 56 | if host, port, err := net.SplitHostPort(hostport); err == nil { 57 | hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) 58 | } 59 | } 60 | _, cancel := context.WithCancel(l.core.ctx) 61 | listener, err := l.listenFor(url, sintf) 62 | if err != nil { 63 | cancel() 64 | return nil, err 65 | } 66 | entry := &Listener{ 67 | Listener: listener, 68 | closed: make(chan struct{}), 69 | } 70 | phony.Block(l, func() { 71 | l._listeners[entry] = cancel 72 | }) 73 | l.core.log.Printf("Multipath listener started on %s", listener.Addr()) 74 | go func() { 75 | defer phony.Block(l, func() { 76 | delete(l._listeners, entry) 77 | }) 78 | for { 79 | conn, err := listener.Accept() 80 | if err != nil { 81 | cancel() 82 | break 83 | } 84 | addr := conn.RemoteAddr().(*net.TCPAddr) 85 | name := fmt.Sprintf("mpath://%s", addr) 86 | info := linkInfoFor("mpath", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) 87 | if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, addr.IP.IsLinkLocalUnicast()); err != nil { 88 | l.core.log.Errorln("Failed to create inbound link:", err) 89 | } 90 | } 91 | _ = listener.Close() 92 | close(entry.closed) 93 | l.core.log.Printf("Multipath listener stopped on %s", listener.Addr()) 94 | }() 95 | return entry, nil 96 | } 97 | 98 | func (l *linkMPATH) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool, force bool) error { 99 | return l.links.create( 100 | conn, // connection 101 | dial, // connection URL 102 | name, // connection name 103 | info, // connection info 104 | incoming, // not incoming 105 | force, // not forced 106 | options, // connection options 107 | ) 108 | } 109 | 110 | // Returns the address of the listener. 111 | func (l *linkMPATH) getAddr() *net.TCPAddr { 112 | // TODO: Fix this, because this will currently only give a single address 113 | // to multicast.go, which obviously is not great, but right now multicast.go 114 | // doesn't have the ability to send more than one address in a packet either 115 | var addr *net.TCPAddr 116 | phony.Block(l, func() { 117 | for listener := range l._listeners { 118 | addr = listener.Addr().(*net.TCPAddr) 119 | } 120 | }) 121 | return addr 122 | } 123 | 124 | func (l *linkMPATH) connFor(url *url.URL, sinterfaces string) (net.Conn, error) { 125 | //Peer url has following format: mpath://host-1:port-1/host-2:port-2.../host-n:port-n 126 | hosts := strings.Split(url.String(), "/")[2:] 127 | remoteTargets := make([]net.Addr, 0) 128 | for _, host := range hosts { 129 | dst, err := net.ResolveTCPAddr("tcp", host) 130 | if err != nil { 131 | l.core.log.Errorln("could not resolve host ", dst.String()) 132 | continue 133 | } 134 | if dst.IP.IsLinkLocalUnicast() { 135 | dst.Zone = sinterfaces 136 | if dst.Zone == "" { 137 | l.core.log.Errorln("link-local address requires a zone in ", dst.String()) 138 | continue 139 | } 140 | } 141 | remoteTargets = append(remoteTargets, dst) 142 | } 143 | 144 | if len(remoteTargets) == 0 { 145 | return nil, fmt.Errorf("no valid target hosts given") 146 | } 147 | 148 | dialers := make([]multipath.Dialer, 0) 149 | trackers := make([]multipath.StatsTracker, 0) 150 | if sinterfaces != "" { 151 | sintfarray := strings.Split(sinterfaces, ",") 152 | for _, dst := range remoteTargets { 153 | for _, sintf := range sintfarray { 154 | addr, err := netip.ParseAddr(sintf) 155 | if err != nil { 156 | l.core.log.Errorln("interface %s address incorrect: %w", sintf, err) 157 | continue 158 | } 159 | src := net.ParseIP(addr.WithZone("").String()) 160 | 161 | dstIp := dst.(*net.TCPAddr).IP 162 | 163 | if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { 164 | continue 165 | } 166 | bothglobal := src.IsGlobalUnicast() == dstIp.IsGlobalUnicast() 167 | bothlinklocal := src.IsLinkLocalUnicast() == dstIp.IsLinkLocalUnicast() 168 | if !bothglobal && !bothlinklocal { 169 | continue 170 | } 171 | if (src.To4() != nil) != (dstIp.To4() != nil) { 172 | continue 173 | } 174 | if bothglobal || bothlinklocal { 175 | td := newOutboundDialer(src, dst) 176 | dialers = append(dialers, td) 177 | trackers = append(trackers, multipath.NullTracker{}) 178 | l.core.log.Printf("added outbound dialer for %s->%s", src.String(), dst.String()) 179 | } 180 | 181 | } 182 | } 183 | } else { 184 | star := net.ParseIP("0.0.0.0") 185 | for _, dst := range remoteTargets { 186 | td := newOutboundDialer(star, dst) 187 | dialers = append(dialers, td) 188 | trackers = append(trackers, multipath.NullTracker{}) 189 | l.core.log.Printf("added outbound dialer for %s", dst.String()) 190 | } 191 | } 192 | if len(dialers) == 0 { 193 | return nil, fmt.Errorf("no suitable source address found on interface %q", sinterfaces) 194 | } 195 | dialer := multipath.NewDialer("mpath", dialers) 196 | //conn, err := dialer.DialContext(l.core.ctx, "tcp", remoteTargets[0].String()) 197 | conn, err := dialer.DialContext(l.core.ctx) 198 | if err != nil { 199 | return nil, err 200 | } 201 | return conn, nil 202 | } 203 | 204 | func (l *linkMPATH) listenFor(url *url.URL, sintf string) (net.Listener, error) { 205 | //Public node url has following format: mpath://ip-1:port-1/ip-2:port-2.../ip-n:port-n 206 | hosts := strings.Split(url.String(), "/")[2:] 207 | localTargets := make([]string, 0) 208 | for _, host := range hosts { 209 | dst, err := net.ResolveTCPAddr("tcp", host) 210 | if err != nil { 211 | l.core.log.Errorln("could not resolve host ", dst.String()) 212 | continue 213 | } 214 | if dst.IP.IsLinkLocalUnicast() { 215 | dst.Zone = sintf 216 | if dst.Zone == "" { 217 | l.core.log.Errorln("link-local address requires a zone in ", dst.String()) 218 | continue 219 | } 220 | } 221 | localTargets = append(localTargets, host) 222 | } 223 | 224 | if len(localTargets) == 0 { 225 | return nil, fmt.Errorf("no valid target hosts given") 226 | } 227 | 228 | listeners := make([]net.Listener, 0) 229 | trackers := make([]multipath.StatsTracker, 0) 230 | for _, lT := range localTargets { 231 | l, err := l.listener.Listen(l.core.ctx, "tcp", lT) 232 | if err != nil { 233 | continue 234 | } 235 | listeners = append(listeners, l) 236 | trackers = append(trackers, multipath.NullTracker{}) 237 | } 238 | listener := multipath.NewListener(listeners, trackers) 239 | 240 | return listener, nil 241 | } 242 | 243 | type targetedDailer struct { 244 | localDialer net.Dialer 245 | remoteAddr net.Addr 246 | } 247 | 248 | func newOutboundDialer(inputLocalAddr net.IP, inputRemoteAddr net.Addr) *targetedDailer { 249 | td := &targetedDailer{ 250 | localDialer: net.Dialer{ 251 | LocalAddr: &net.TCPAddr{ 252 | IP: inputLocalAddr, 253 | Port: 0, 254 | }, 255 | }, 256 | remoteAddr: inputRemoteAddr, 257 | } 258 | return td 259 | } 260 | 261 | func (td *targetedDailer) DialContext(ctx context.Context) (net.Conn, error) { 262 | conn, err := td.localDialer.DialContext(ctx, "tcp", td.remoteAddr.String()) 263 | if err != nil { 264 | //l.core.log.Errorln("failed to dial to %v: %v", td.remoteAddr.String(), err) 265 | return nil, err 266 | } 267 | //l.core.log.Printf("Dialed to %v->%v", conn.LocalAddr(), td.remoteAddr.String()) 268 | 269 | return conn, err 270 | } 271 | 272 | func (td *targetedDailer) Label() string { 273 | return fmt.Sprintf("%s|%s", td.localDialer.LocalAddr, td.remoteAddr) 274 | } 275 | -------------------------------------------------------------------------------- /src/core/link_mpath_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 13 | 14 | func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error { 15 | var control error 16 | var recvanyif error 17 | 18 | control = c.Control(func(fd uintptr) { 19 | // sys/socket.h: #define SO_RECV_ANYIF 0x1104 20 | recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) 21 | }) 22 | 23 | switch { 24 | case recvanyif != nil: 25 | return recvanyif 26 | default: 27 | return control 28 | } 29 | } 30 | 31 | func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error { 32 | return t.tcpContext 33 | } 34 | -------------------------------------------------------------------------------- /src/core/link_mpath_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 13 | 14 | func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error { 15 | var control error 16 | var bbr error 17 | 18 | control = c.Control(func(fd uintptr) { 19 | bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") 20 | }) 21 | 22 | // Log any errors 23 | if bbr != nil { 24 | t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr) 25 | } 26 | if control != nil { 27 | t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control) 28 | } 29 | 30 | // Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal 31 | return nil 32 | } 33 | 34 | //nolint:unused 35 | func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error { 36 | return func(network, address string, c syscall.RawConn) error { 37 | var err error 38 | btd := func(fd uintptr) { 39 | err = unix.BindToDevice(int(fd), sintf) 40 | } 41 | _ = c.Control(btd) 42 | if err != nil { 43 | t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) 44 | } 45 | return t.tcpContext(network, address, c) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/core/link_mpath_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux 2 | // +build !darwin,!linux 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 11 | 12 | func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error { 13 | return nil 14 | } 15 | 16 | func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error { 17 | return t.tcpContext 18 | } 19 | -------------------------------------------------------------------------------- /src/core/link_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package core 5 | 6 | import ( 7 | "encoding/hex" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | "net/url" 13 | "strconv" 14 | 15 | "github.com/Arceliar/phony" 16 | //"github.com/Arceliar/phony" // TODO? use instead of mutexes 17 | ) 18 | 19 | type links struct { 20 | phony.Inbox 21 | core *Core 22 | tcp *linkTCP // TCP interface support 23 | tls *linkTLS // TLS interface support 24 | unix *linkUNIX // UNIX interface support 25 | socks *linkSOCKS // SOCKS interface support 26 | mpath *linkMPATH // Multipath interface support 27 | _links map[linkInfo]*link // *link is nil if connection in progress 28 | // TODO timeout (to remove from switch), read from config.ReadTimeout 29 | } 30 | 31 | func (l *links) init(c *Core) error { 32 | l.core = c 33 | l.tcp = l.newLinkTCP() 34 | l.tls = l.newLinkTLS(l.tcp) 35 | l.unix = l.newLinkUNIX() 36 | l.socks = l.newLinkSOCKS() 37 | l.mpath = l.newLinkMPATH() 38 | l._links = make(map[linkInfo]*link) 39 | 40 | var listeners []ListenAddress 41 | phony.Block(c, func() { 42 | listeners = make([]ListenAddress, 0, len(c.config._listeners)) 43 | for listener := range c.config._listeners { 44 | listeners = append(listeners, listener) 45 | } 46 | }) 47 | 48 | return nil 49 | } 50 | 51 | func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInfo, err error) { 52 | info = linkInfoFor(u.Scheme, sintf, u.Host) 53 | if l.isConnectedTo(info) { 54 | if errch != nil { 55 | close(errch) // already connected, no error 56 | } 57 | return info, nil 58 | } 59 | options := linkOptions{ 60 | pinnedEd25519Keys: map[keyArray]struct{}{}, 61 | } 62 | for _, pubkey := range u.Query()["key"] { 63 | sigPub, err := hex.DecodeString(pubkey) 64 | if err != nil { 65 | if errch != nil { 66 | close(errch) 67 | } 68 | return info, fmt.Errorf("pinned key contains invalid hex characters") 69 | } 70 | var sigPubKey keyArray 71 | copy(sigPubKey[:], sigPub) 72 | options.pinnedEd25519Keys[sigPubKey] = struct{}{} 73 | } 74 | if p := u.Query().Get("priority"); p != "" { 75 | pi, err := strconv.ParseUint(p, 10, 8) 76 | if err != nil { 77 | if errch != nil { 78 | close(errch) 79 | } 80 | return info, fmt.Errorf("priority invalid: %w", err) 81 | } 82 | options.priority = uint8(pi) 83 | } 84 | switch info.linkType { 85 | case "tcp": 86 | go func() { 87 | if errch != nil { 88 | defer close(errch) 89 | } 90 | if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF { 91 | l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err) 92 | if errch != nil { 93 | errch <- err 94 | } 95 | } 96 | }() 97 | 98 | case "socks": 99 | go func() { 100 | if errch != nil { 101 | defer close(errch) 102 | } 103 | if err := l.socks.dial(u, options); err != nil && err != io.EOF { 104 | l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err) 105 | if errch != nil { 106 | errch <- err 107 | } 108 | } 109 | }() 110 | 111 | case "tls": 112 | // SNI headers must contain hostnames and not IP addresses, so we must make sure 113 | // that we do not populate the SNI with an IP literal. We do this by splitting 114 | // the host-port combo from the query option and then seeing if it parses to an 115 | // IP address successfully or not. 116 | var tlsSNI string 117 | if sni := u.Query().Get("sni"); sni != "" { 118 | if net.ParseIP(sni) == nil { 119 | tlsSNI = sni 120 | } 121 | } 122 | // If the SNI is not configured still because the above failed then we'll try 123 | // again but this time we'll use the host part of the peering URI instead. 124 | if tlsSNI == "" { 125 | if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { 126 | tlsSNI = host 127 | } 128 | } 129 | go func() { 130 | if errch != nil { 131 | defer close(errch) 132 | } 133 | if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF { 134 | l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err) 135 | if errch != nil { 136 | errch <- err 137 | } 138 | } 139 | }() 140 | 141 | case "unix": 142 | go func() { 143 | if errch != nil { 144 | defer close(errch) 145 | } 146 | if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF { 147 | l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err) 148 | if errch != nil { 149 | errch <- err 150 | } 151 | } 152 | }() 153 | 154 | case "mpath": 155 | go func() { 156 | if errch != nil { 157 | defer close(errch) 158 | } 159 | if err := l.mpath.dial(u, options, sintf); err != nil && err != io.EOF { 160 | l.core.log.Warnf("Failed to dial MPATH %s: %s\n", u.Host, err) 161 | if errch != nil { 162 | errch <- err 163 | } 164 | } 165 | }() 166 | 167 | default: 168 | if errch != nil { 169 | close(errch) 170 | } 171 | return info, errors.New("unknown call scheme: " + u.Scheme) 172 | } 173 | return info, nil 174 | } 175 | 176 | func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { 177 | var listener *Listener 178 | var err error 179 | switch u.Scheme { 180 | case "tcp": 181 | listener, err = l.tcp.listen(u, sintf) 182 | case "tls": 183 | listener, err = l.tls.listen(u, sintf) 184 | case "unix": 185 | listener, err = l.unix.listen(u, sintf) 186 | case "mpath": 187 | listener, err = l.mpath.listen(u, sintf) 188 | default: 189 | return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme) 190 | } 191 | return listener, err 192 | } 193 | -------------------------------------------------------------------------------- /src/core/link_sctp_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package core 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "net" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/Arceliar/phony" 16 | sctp "github.com/vikulin/sctp" 17 | ) 18 | 19 | type linkSCTP struct { 20 | phony.Inbox 21 | *links 22 | listener *net.ListenConfig 23 | _listeners map[*Listener]context.CancelFunc 24 | } 25 | 26 | func (l *links) newLinkSCTP() *linkSCTP { 27 | lt := &linkSCTP{ 28 | links: l, 29 | listener: &net.ListenConfig{ 30 | KeepAlive: -1, 31 | }, 32 | _listeners: map[*Listener]context.CancelFunc{}, 33 | } 34 | return lt 35 | } 36 | 37 | func (l *linkSCTP) dial(url *url.URL, options linkOptions, sintf string) error { 38 | info := linkInfoFor("sctp", sintf, strings.SplitN(url.Host, "%", 2)[0]) 39 | if l.links.isConnectedTo(info) { 40 | return nil 41 | } 42 | host, port, err := net.SplitHostPort(url.Host) 43 | if err != nil { 44 | return err 45 | } 46 | dst, err := net.ResolveIPAddr("ip", host) 47 | if err != nil { 48 | return err 49 | } 50 | raddress := l.getAddress(dst.String() + ":" + port) 51 | var conn net.Conn 52 | laddress := l.getAddress("0.0.0.0:0") 53 | conn, err = sctp.NewSCTPConnection(laddress, laddress.AddressFamily, sctp.InitMsg{NumOstreams: 2, MaxInstreams: 2, MaxAttempts: 2, MaxInitTimeout: 5}, sctp.OneToOne, false) 54 | if err != nil { 55 | return err 56 | } 57 | err = conn.(*sctp.SCTPConn).Connect(raddress) 58 | if err != nil { 59 | return err 60 | } 61 | //conn.(*sctp.SCTPConn).SetWriteBuffer(324288) 62 | //conn.(*sctp.SCTPConn).SetReadBuffer(324288) 63 | //wbuf, _ := conn.(*sctp.SCTPConn).GetWriteBuffer() 64 | //rbuf, _ := conn.(*sctp.SCTPConn).GetReadBuffer() 65 | 66 | //l.core.log.Printf("Read buffer %d", rbuf) 67 | //l.core.log.Printf("Write buffer %d", wbuf) 68 | err = conn.(*sctp.SCTPConn).SetEvents(sctp.SCTP_EVENT_DATA_IO) 69 | if err != nil { 70 | return err 71 | } 72 | dial := &linkDial{ 73 | url: url, 74 | sintf: sintf, 75 | } 76 | return l.handler(dial, url.String(), info, conn, options, false, false) 77 | } 78 | 79 | func (l *linkSCTP) listen(url *url.URL, sintf string) (*Listener, error) { 80 | //_, cancel := context.WithCancel(l.core.ctx) 81 | /* 82 | hostport := url.Host 83 | if sintf != "" { 84 | if host, port, err := net.SplitHostPort(hostport); err == nil { 85 | hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) 86 | } 87 | } 88 | */ 89 | addr := l.getAddress(url.Host) 90 | listener, err := sctp.NewSCTPListener(addr, sctp.InitMsg{NumOstreams: 2, MaxInstreams: 2, MaxAttempts: 2, MaxInitTimeout: 5}, sctp.OneToOne, false) 91 | 92 | if err != nil { 93 | //cancel() 94 | return nil, err 95 | } 96 | err = listener.SetEvents(sctp.SCTP_EVENT_DATA_IO) 97 | if err != nil { 98 | return nil, err 99 | } 100 | entry := &Listener{ 101 | Listener: listener, 102 | closed: make(chan struct{}), 103 | } 104 | //phony.Block(l, func() { 105 | // l._listeners[entry] = cancel 106 | //}) 107 | l.core.log.Printf("SCTP listener started on %s", listener.Addr()) 108 | go func() { 109 | defer phony.Block(l, func() { 110 | delete(l._listeners, entry) 111 | }) 112 | for { 113 | conn, err := listener.Accept() 114 | if err != nil { 115 | //cancel() 116 | break 117 | } 118 | addr := conn.RemoteAddr().(*sctp.SCTPAddr) 119 | ips, err := json.Marshal(addr.IPAddrs) 120 | if err != nil { 121 | break 122 | } 123 | name := fmt.Sprintf("sctp://%s", ips) 124 | info := linkInfoFor("sctp", sintf, string(ips)) 125 | //conn.(*sctp.SCTPConn).SetWriteBuffer(324288) 126 | //conn.(*sctp.SCTPConn).SetReadBuffer(324288) 127 | wbuf, _ := conn.(*sctp.SCTPConn).GetWriteBuffer() 128 | rbuf, _ := conn.(*sctp.SCTPConn).GetReadBuffer() 129 | 130 | l.core.log.Printf("Read buffer %d", rbuf) 131 | l.core.log.Printf("Write buffer %d", wbuf) 132 | if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, addr.IPAddrs[0].IP.IsLinkLocalUnicast()); err != nil { 133 | l.core.log.Errorln("Failed to create inbound link:", err) 134 | } 135 | } 136 | _ = listener.Close() 137 | close(entry.closed) 138 | l.core.log.Printf("SCTP listener stopped on %s", listener.Addr()) 139 | }() 140 | return entry, nil 141 | } 142 | 143 | func (l *linkSCTP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { 144 | return l.links.create( 145 | conn, // connection 146 | dial, // connection URL 147 | name, // connection name 148 | info, // connection info 149 | incoming, // not incoming 150 | force, // not forced 151 | options, // connection options 152 | ) 153 | } 154 | 155 | // Returns the address of the listener. 156 | // 157 | //nolint:unused 158 | func (l *linkSCTP) getAddr() *net.TCPAddr { 159 | // TODO: Fix this, because this will currently only give a single address 160 | // to multicast.go, which obviously is not great, but right now multicast.go 161 | // doesn't have the ability to send more than one address in a packet either 162 | var addr *net.TCPAddr 163 | phony.Block(l, func() { 164 | for listener := range l._listeners { 165 | addr = listener.Addr().(*net.TCPAddr) 166 | } 167 | }) 168 | return addr 169 | } 170 | 171 | // SCTP infrastructure 172 | func (l *linkSCTP) getAddress(host string) *sctp.SCTPAddr { 173 | 174 | //sctp supports multihoming but current implementation reuires only one path 175 | ips := []net.IPAddr{} 176 | ip, port, err := net.SplitHostPort(host) 177 | if err != nil { 178 | panic(err) 179 | } 180 | for _, i := range strings.Split(ip, ",") { 181 | if a, err := net.ResolveIPAddr("ip", i); err == nil { 182 | l.core.log.Printf("Resolved address '%s' to %s", i, a) 183 | ips = append(ips, *a) 184 | } else { 185 | l.core.log.Errorln("Error resolving address '%s': %v", i, err) 186 | } 187 | } 188 | p, _ := strconv.Atoi(port) 189 | addr := &sctp.SCTPAddr{ 190 | IPAddrs: ips, 191 | Port: p, 192 | } 193 | return addr 194 | } 195 | -------------------------------------------------------------------------------- /src/core/link_socks.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "strings" 8 | 9 | "golang.org/x/net/proxy" 10 | ) 11 | 12 | type linkSOCKS struct { 13 | *links 14 | } 15 | 16 | func (l *links) newLinkSOCKS() *linkSOCKS { 17 | lt := &linkSOCKS{ 18 | links: l, 19 | } 20 | return lt 21 | } 22 | 23 | func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error { 24 | info := linkInfoFor("socks", "", url.Path) 25 | if l.links.isConnectedTo(info) { 26 | return nil 27 | } 28 | proxyAuth := &proxy.Auth{} 29 | proxyAuth.User = url.User.Username() 30 | proxyAuth.Password, _ = url.User.Password() 31 | dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct) 32 | if err != nil { 33 | return fmt.Errorf("failed to configure proxy") 34 | } 35 | pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/") 36 | conn, err := dialer.Dial("tcp", pathtokens[0]) 37 | if err != nil { 38 | return err 39 | } 40 | dial := &linkDial{ 41 | url: url, 42 | } 43 | return l.handler(dial, info, conn, options, false) 44 | } 45 | 46 | func (l *linkSOCKS) handler(dial *linkDial, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { 47 | return l.links.create( 48 | conn, // connection 49 | dial, // connection URL 50 | dial.url.String(), // connection name 51 | info, // connection info 52 | incoming, // not incoming 53 | false, // not forced 54 | options, // connection options 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/core/link_tcp.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/url" 8 | "strings" 9 | "time" 10 | 11 | "github.com/wlynxg/anet" 12 | 13 | "github.com/Arceliar/phony" 14 | ) 15 | 16 | type linkTCP struct { 17 | phony.Inbox 18 | *links 19 | listener *net.ListenConfig 20 | _listeners map[*Listener]context.CancelFunc 21 | } 22 | 23 | func (l *links) newLinkTCP() *linkTCP { 24 | lt := &linkTCP{ 25 | links: l, 26 | listener: &net.ListenConfig{ 27 | KeepAlive: -1, 28 | }, 29 | _listeners: map[*Listener]context.CancelFunc{}, 30 | } 31 | lt.listener.Control = lt.tcpContext 32 | return lt 33 | } 34 | 35 | func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { 36 | addr, err := net.ResolveTCPAddr("tcp", url.Host) 37 | if err != nil { 38 | return err 39 | } 40 | dialer, err := l.dialerFor(addr, sintf) 41 | if err != nil { 42 | return err 43 | } 44 | info := linkInfoFor("tcp", sintf, tcpIDFor(dialer.LocalAddr, addr)) 45 | if l.links.isConnectedTo(info) { 46 | return nil 47 | } 48 | conn, err := dialer.DialContext(l.core.ctx, "tcp", addr.String()) 49 | if err != nil { 50 | return err 51 | } 52 | name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") 53 | dial := &linkDial{ 54 | url: url, 55 | sintf: sintf, 56 | } 57 | return l.handler(dial, name, info, conn, options, false, false) 58 | } 59 | 60 | func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { 61 | ctx, cancel := context.WithCancel(l.core.ctx) 62 | hostport := url.Host 63 | if sintf != "" { 64 | if host, port, err := net.SplitHostPort(hostport); err == nil { 65 | hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) 66 | } 67 | } 68 | listener, err := l.listener.Listen(ctx, "tcp", hostport) 69 | if err != nil { 70 | cancel() 71 | return nil, err 72 | } 73 | entry := &Listener{ 74 | Listener: listener, 75 | closed: make(chan struct{}), 76 | } 77 | phony.Block(l, func() { 78 | l._listeners[entry] = cancel 79 | }) 80 | l.core.log.Printf("TCP listener started on %s", listener.Addr()) 81 | go func() { 82 | defer phony.Block(l, func() { 83 | delete(l._listeners, entry) 84 | }) 85 | for { 86 | conn, err := listener.Accept() 87 | if err != nil { 88 | cancel() 89 | break 90 | } 91 | laddr := conn.LocalAddr().(*net.TCPAddr) 92 | raddr := conn.RemoteAddr().(*net.TCPAddr) 93 | name := fmt.Sprintf("tcp://%s", raddr) 94 | info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr)) 95 | if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { 96 | l.core.log.Errorln("Failed to create inbound link:", err) 97 | } 98 | } 99 | _ = listener.Close() 100 | close(entry.closed) 101 | l.core.log.Printf("TCP listener stopped on %s", listener.Addr()) 102 | }() 103 | return entry, nil 104 | } 105 | 106 | func (l *linkTCP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { 107 | return l.links.create( 108 | conn, // connection 109 | dial, // connection URL 110 | name, // connection name 111 | info, // connection info 112 | incoming, // not incoming 113 | force, // not forced 114 | options, // connection options 115 | ) 116 | } 117 | 118 | // Returns the address of the listener. 119 | func (l *linkTCP) getAddr() *net.TCPAddr { 120 | // TODO: Fix this, because this will currently only give a single address 121 | // to multicast.go, which obviously is not great, but right now multicast.go 122 | // doesn't have the ability to send more than one address in a packet either 123 | var addr *net.TCPAddr 124 | phony.Block(l, func() { 125 | for listener := range l._listeners { 126 | addr = listener.Addr().(*net.TCPAddr) 127 | } 128 | }) 129 | return addr 130 | } 131 | 132 | func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) { 133 | if dst.IP.IsLinkLocalUnicast() { 134 | if sintf != "" { 135 | dst.Zone = sintf 136 | } 137 | if dst.Zone == "" { 138 | return nil, fmt.Errorf("link-local address requires a zone") 139 | } 140 | } 141 | dialer := &net.Dialer{ 142 | Timeout: time.Second * 5, 143 | KeepAlive: -1, 144 | Control: l.tcpContext, 145 | } 146 | if sintf != "" { 147 | i, err := anet.Interfaces() 148 | if err != nil { 149 | return nil, fmt.Errorf("interfaces error: %w", err) 150 | } 151 | var ief net.Interface 152 | found := false 153 | for _, ie := range i { 154 | if ie.Name == sintf { 155 | ief = ie 156 | found = true 157 | break 158 | } 159 | } 160 | if !found { 161 | return nil, fmt.Errorf("interface %s not found", sintf) 162 | } 163 | dialer.Control = l.getControl(sintf) 164 | 165 | if ief.Flags&net.FlagUp == 0 { 166 | return nil, fmt.Errorf("interface %q is not up", sintf) 167 | } 168 | addrs, err := anet.InterfaceAddrsByInterface(&ief) 169 | if err != nil { 170 | return nil, fmt.Errorf("interface %q addresses not available: %w", sintf, err) 171 | } 172 | for addrindex, addr := range addrs { 173 | src, _, err := net.ParseCIDR(addr.String()) 174 | if err != nil { 175 | continue 176 | } 177 | if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { 178 | continue 179 | } 180 | bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast() 181 | bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast() 182 | if !bothglobal && !bothlinklocal { 183 | continue 184 | } 185 | if (src.To4() != nil) != (dst.IP.To4() != nil) { 186 | continue 187 | } 188 | if bothglobal || bothlinklocal || addrindex == len(addrs)-1 { 189 | dialer.LocalAddr = &net.TCPAddr{ 190 | IP: src, 191 | Port: 0, 192 | Zone: sintf, 193 | } 194 | break 195 | } 196 | } 197 | if dialer.LocalAddr == nil { 198 | return nil, fmt.Errorf("no suitable source address found on interface %q", sintf) 199 | } 200 | } 201 | return dialer, nil 202 | } 203 | 204 | func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string { 205 | if remoteAddr.IP.IsLinkLocalUnicast() { 206 | // Nodes discovered via multicast — include the IP only. 207 | return remoteAddr.IP.String() 208 | } 209 | if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) { 210 | // Nodes running on the same host — include both the IP and port. 211 | return remoteAddr.String() 212 | } 213 | // Nodes connected remotely — include both the IP and port. 214 | return remoteAddr.String() 215 | } 216 | -------------------------------------------------------------------------------- /src/core/link_tcp_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 13 | 14 | func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { 15 | var control error 16 | var recvanyif error 17 | 18 | control = c.Control(func(fd uintptr) { 19 | // sys/socket.h: #define SO_RECV_ANYIF 0x1104 20 | recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) 21 | }) 22 | 23 | switch { 24 | case recvanyif != nil: 25 | return recvanyif 26 | default: 27 | return control 28 | } 29 | } 30 | 31 | func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { 32 | return t.tcpContext 33 | } 34 | -------------------------------------------------------------------------------- /src/core/link_tcp_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 13 | 14 | func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { 15 | var control error 16 | var bbr error 17 | 18 | control = c.Control(func(fd uintptr) { 19 | bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") 20 | }) 21 | 22 | // Log any errors 23 | if bbr != nil { 24 | t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr) 25 | } 26 | if control != nil { 27 | t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control) 28 | } 29 | 30 | // Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal 31 | return nil 32 | } 33 | 34 | func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { 35 | return func(network, address string, c syscall.RawConn) error { 36 | var err error 37 | btd := func(fd uintptr) { 38 | err = unix.BindToDevice(int(fd), sintf) 39 | } 40 | _ = c.Control(btd) 41 | if err != nil { 42 | t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) 43 | } 44 | return t.tcpContext(network, address, c) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/link_tcp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux 2 | // +build !darwin,!linux 3 | 4 | package core 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go 11 | 12 | func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { 13 | return nil 14 | } 15 | 16 | func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { 17 | return t.tcpContext 18 | } 19 | -------------------------------------------------------------------------------- /src/core/link_tls.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/rand" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/hex" 11 | "encoding/pem" 12 | "fmt" 13 | "math/big" 14 | "net" 15 | "net/url" 16 | "strings" 17 | "time" 18 | 19 | "github.com/Arceliar/phony" 20 | ) 21 | 22 | type linkTLS struct { 23 | phony.Inbox 24 | *links 25 | tcp *linkTCP 26 | listener *net.ListenConfig 27 | config *tls.Config 28 | _listeners map[*Listener]context.CancelFunc 29 | } 30 | 31 | func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { 32 | lt := &linkTLS{ 33 | links: l, 34 | tcp: tcp, 35 | listener: &net.ListenConfig{ 36 | Control: tcp.tcpContext, 37 | KeepAlive: -1, 38 | }, 39 | _listeners: map[*Listener]context.CancelFunc{}, 40 | } 41 | var err error 42 | lt.config, err = lt.generateConfig() 43 | if err != nil { 44 | panic(err) 45 | } 46 | return lt 47 | } 48 | 49 | func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error { 50 | addr, err := net.ResolveTCPAddr("tcp", url.Host) 51 | if err != nil { 52 | return err 53 | } 54 | dialer, err := l.tcp.dialerFor(addr, sintf) 55 | if err != nil { 56 | return err 57 | } 58 | info := linkInfoFor("tls", sintf, tcpIDFor(dialer.LocalAddr, addr)) 59 | if l.links.isConnectedTo(info) { 60 | return nil 61 | } 62 | tlsconfig := l.config.Clone() 63 | tlsconfig.ServerName = sni 64 | tlsdialer := &tls.Dialer{ 65 | NetDialer: dialer, 66 | Config: tlsconfig, 67 | } 68 | conn, err := tlsdialer.DialContext(l.core.ctx, "tcp", addr.String()) 69 | if err != nil { 70 | return err 71 | } 72 | name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") 73 | dial := &linkDial{ 74 | url: url, 75 | sintf: sintf, 76 | } 77 | return l.handler(dial, name, info, conn, options, false, false) 78 | } 79 | 80 | func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { 81 | ctx, cancel := context.WithCancel(l.core.ctx) 82 | hostport := url.Host 83 | if sintf != "" { 84 | if host, port, err := net.SplitHostPort(hostport); err == nil { 85 | hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) 86 | } 87 | } 88 | listener, err := l.listener.Listen(ctx, "tcp", hostport) 89 | if err != nil { 90 | cancel() 91 | return nil, err 92 | } 93 | tlslistener := tls.NewListener(listener, l.config) 94 | entry := &Listener{ 95 | Listener: tlslistener, 96 | closed: make(chan struct{}), 97 | } 98 | phony.Block(l, func() { 99 | l._listeners[entry] = cancel 100 | }) 101 | l.core.log.Printf("TLS listener started on %s", listener.Addr()) 102 | go func() { 103 | defer phony.Block(l, func() { 104 | delete(l._listeners, entry) 105 | }) 106 | for { 107 | conn, err := tlslistener.Accept() 108 | if err != nil { 109 | cancel() 110 | break 111 | } 112 | laddr := conn.LocalAddr().(*net.TCPAddr) 113 | raddr := conn.RemoteAddr().(*net.TCPAddr) 114 | name := fmt.Sprintf("tls://%s", raddr) 115 | info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr)) 116 | if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { 117 | l.core.log.Errorln("Failed to create inbound link:", err) 118 | } 119 | } 120 | _ = tlslistener.Close() 121 | close(entry.closed) 122 | l.core.log.Printf("TLS listener stopped on %s", listener.Addr()) 123 | }() 124 | return entry, nil 125 | } 126 | 127 | // RFC5280 section 4.1.2.5 128 | var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC) 129 | 130 | func (l *linkTLS) generateConfig() (*tls.Config, error) { 131 | certBuf := &bytes.Buffer{} 132 | cert := x509.Certificate{ 133 | SerialNumber: big.NewInt(1), 134 | Subject: pkix.Name{ 135 | CommonName: hex.EncodeToString(l.links.core.public[:]), 136 | }, 137 | NotBefore: time.Now(), 138 | NotAfter: notAfterNeverExpires, 139 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 140 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 141 | BasicConstraintsValid: true, 142 | } 143 | 144 | certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | if err := pem.Encode(certBuf, &pem.Block{ 150 | Type: "CERTIFICATE", 151 | Bytes: certbytes, 152 | }); err != nil { 153 | return nil, err 154 | } 155 | 156 | rootCAs := x509.NewCertPool() 157 | rootCAs.AppendCertsFromPEM(certbytes) 158 | 159 | return &tls.Config{ 160 | RootCAs: rootCAs, 161 | Certificates: []tls.Certificate{ 162 | { 163 | Certificate: [][]byte{certbytes}, 164 | PrivateKey: l.links.core.secret, 165 | }, 166 | }, 167 | InsecureSkipVerify: true, 168 | MinVersion: tls.VersionTLS13, 169 | }, nil 170 | } 171 | 172 | func (l *linkTLS) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { 173 | return l.tcp.handler(dial, name, info, conn, options, incoming, force) 174 | } 175 | -------------------------------------------------------------------------------- /src/core/link_unix.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/Arceliar/phony" 10 | ) 11 | 12 | type linkUNIX struct { 13 | phony.Inbox 14 | *links 15 | dialer *net.Dialer 16 | listener *net.ListenConfig 17 | _listeners map[*Listener]context.CancelFunc 18 | } 19 | 20 | func (l *links) newLinkUNIX() *linkUNIX { 21 | lt := &linkUNIX{ 22 | links: l, 23 | dialer: &net.Dialer{ 24 | Timeout: time.Second * 5, 25 | KeepAlive: -1, 26 | }, 27 | listener: &net.ListenConfig{ 28 | KeepAlive: -1, 29 | }, 30 | _listeners: map[*Listener]context.CancelFunc{}, 31 | } 32 | return lt 33 | } 34 | 35 | func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error { 36 | info := linkInfoFor("unix", "", url.Path) 37 | if l.links.isConnectedTo(info) { 38 | return nil 39 | } 40 | addr, err := net.ResolveUnixAddr("unix", url.Path) 41 | if err != nil { 42 | return err 43 | } 44 | conn, err := l.dialer.DialContext(l.core.ctx, "unix", addr.String()) 45 | if err != nil { 46 | return err 47 | } 48 | dial := &linkDial{ 49 | url: url, 50 | } 51 | return l.handler(dial, url.String(), info, conn, options, false) 52 | } 53 | 54 | func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { 55 | ctx, cancel := context.WithCancel(l.core.ctx) 56 | listener, err := l.listener.Listen(ctx, "unix", url.Path) 57 | if err != nil { 58 | cancel() 59 | return nil, err 60 | } 61 | entry := &Listener{ 62 | Listener: listener, 63 | closed: make(chan struct{}), 64 | } 65 | phony.Block(l, func() { 66 | l._listeners[entry] = cancel 67 | }) 68 | l.core.log.Printf("UNIX listener started on %s", listener.Addr()) 69 | go func() { 70 | defer phony.Block(l, func() { 71 | delete(l._listeners, entry) 72 | }) 73 | for { 74 | conn, err := listener.Accept() 75 | if err != nil { 76 | cancel() 77 | break 78 | } 79 | info := linkInfoFor("unix", "", url.String()) 80 | if err = l.handler(nil, url.String(), info, conn, linkOptionsForListener(url), true); err != nil { 81 | l.core.log.Errorln("Failed to create inbound link:", err) 82 | } 83 | } 84 | _ = listener.Close() 85 | close(entry.closed) 86 | l.core.log.Printf("UNIX listener stopped on %s", listener.Addr()) 87 | }() 88 | return entry, nil 89 | } 90 | 91 | func (l *linkUNIX) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { 92 | return l.links.create( 93 | conn, // connection 94 | dial, // connection URL 95 | name, // connection name 96 | info, // connection info 97 | incoming, // not incoming 98 | false, // not forced 99 | options, // connection options 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /src/core/nodeinfo.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "runtime" 8 | "time" 9 | 10 | iwt "github.com/Arceliar/ironwood/types" 11 | "github.com/Arceliar/phony" 12 | 13 | //"github.com/RiV-chain/RiV-mesh/src/crypto" 14 | 15 | "github.com/RiV-chain/RiV-mesh/src/version" 16 | ) 17 | 18 | type nodeinfo struct { 19 | phony.Inbox 20 | proto *protoHandler 21 | myNodeInfo json.RawMessage 22 | callbacks map[keyArray]nodeinfoCallback 23 | } 24 | 25 | type nodeinfoCallback struct { 26 | call func(nodeinfo json.RawMessage) 27 | created time.Time 28 | } 29 | 30 | // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep 31 | // the cache/callback maps clean of stale entries 32 | func (m *nodeinfo) init(proto *protoHandler) { 33 | m.Act(nil, func() { 34 | m._init(proto) 35 | }) 36 | } 37 | 38 | func (m *nodeinfo) _init(proto *protoHandler) { 39 | m.proto = proto 40 | m.callbacks = make(map[keyArray]nodeinfoCallback) 41 | m._cleanup() 42 | } 43 | 44 | func (m *nodeinfo) _cleanup() { 45 | for boxPubKey, callback := range m.callbacks { 46 | if time.Since(callback.created) > time.Minute { 47 | delete(m.callbacks, boxPubKey) 48 | } 49 | } 50 | time.AfterFunc(time.Second*30, func() { 51 | m.Act(nil, m._cleanup) 52 | }) 53 | } 54 | 55 | func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo json.RawMessage)) { 56 | m.callbacks[sender] = nodeinfoCallback{ 57 | created: time.Now(), 58 | call: call, 59 | } 60 | } 61 | 62 | // Handles the callback, if there is one 63 | func (m *nodeinfo) _callback(sender keyArray, nodeinfo json.RawMessage) { 64 | if callback, ok := m.callbacks[sender]; ok { 65 | callback.call(nodeinfo) 66 | delete(m.callbacks, sender) 67 | } 68 | } 69 | 70 | func (m *nodeinfo) _getNodeInfo() json.RawMessage { 71 | return m.myNodeInfo 72 | } 73 | 74 | // Set the current node's nodeinfo 75 | func (m *nodeinfo) setNodeInfo(given map[string]interface{}, privacy bool) (err error) { 76 | phony.Block(m, func() { 77 | err = m._setNodeInfo(given, privacy) 78 | }) 79 | return 80 | } 81 | 82 | func (m *nodeinfo) _setNodeInfo(given map[string]interface{}, privacy bool) error { 83 | newnodeinfo := make(map[string]interface{}, len(given)) 84 | for k, v := range given { 85 | newnodeinfo[k] = v 86 | } 87 | if !privacy { 88 | newnodeinfo["buildname"] = version.BuildName() 89 | newnodeinfo["buildversion"] = version.BuildVersion() 90 | newnodeinfo["buildplatform"] = runtime.GOOS 91 | newnodeinfo["buildarch"] = runtime.GOARCH 92 | } 93 | newjson, err := json.Marshal(newnodeinfo) 94 | switch { 95 | case err != nil: 96 | return fmt.Errorf("NodeInfo marshalling failed: %w", err) 97 | case len(newjson) > 16384: 98 | return fmt.Errorf("NodeInfo exceeds max length of 16384 bytes") 99 | default: 100 | m.myNodeInfo = newjson 101 | return nil 102 | } 103 | } 104 | 105 | func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo json.RawMessage)) { 106 | m.Act(from, func() { 107 | m._sendReq(key, callback) 108 | }) 109 | } 110 | 111 | func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo json.RawMessage)) { 112 | if callback != nil { 113 | m._addCallback(key, callback) 114 | } 115 | _, _ = m.proto.core.PacketConn.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) 116 | } 117 | 118 | func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { 119 | m.Act(from, func() { 120 | m._sendRes(key) 121 | }) 122 | } 123 | 124 | func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info json.RawMessage) { 125 | m.Act(from, func() { 126 | m._callback(key, info) 127 | }) 128 | } 129 | 130 | func (m *nodeinfo) _sendRes(key keyArray) { 131 | bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) 132 | _, _ = m.proto.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) 133 | } 134 | 135 | // Admin socket stuff 136 | 137 | type GetNodeInfoRequest struct { 138 | Key string `json:"key"` 139 | } 140 | type GetNodeInfoResponse map[string]json.RawMessage 141 | 142 | func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) { 143 | var req GetNodeInfoRequest 144 | if err := json.Unmarshal(in, &req); err != nil { 145 | return nil, err 146 | } 147 | if req.Key == "" { 148 | return nil, fmt.Errorf("No remote public key supplied") 149 | } 150 | var key keyArray 151 | var kbs []byte 152 | var err error 153 | if kbs, err = hex.DecodeString(req.Key); err != nil { 154 | return nil, fmt.Errorf("Failed to decode public key: %w", err) 155 | } 156 | copy(key[:], kbs) 157 | ch := make(chan []byte, 1) 158 | m.sendReq(nil, key, func(info json.RawMessage) { 159 | ch <- info 160 | }) 161 | timer := time.NewTimer(6 * time.Second) 162 | defer timer.Stop() 163 | select { 164 | case <-timer.C: 165 | return nil, ErrTimeout 166 | case info := <-ch: 167 | var msg json.RawMessage 168 | if err := msg.UnmarshalJSON(info); err != nil { 169 | return nil, err 170 | } 171 | key := hex.EncodeToString(kbs[:]) 172 | res := GetNodeInfoResponse{key: msg} 173 | return res, nil 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/core/options.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/ed25519" 5 | ) 6 | 7 | func (c *Core) _applyOption(opt SetupOption) { 8 | switch v := opt.(type) { 9 | case Peer: 10 | c.config._peers[v] = nil 11 | case ListenAddress: 12 | c.config._listeners[v] = struct{}{} 13 | case NodeInfo: 14 | c.config.nodeinfo = v 15 | case NodeInfoPrivacy: 16 | c.config.nodeinfoPrivacy = v 17 | case NetworkDomain: 18 | c.config.networkdomain = v 19 | case AllowedPublicKey: 20 | pk := [32]byte{} 21 | copy(pk[:], v) 22 | c.config._allowedPublicKeys[pk] = struct{}{} 23 | } 24 | } 25 | 26 | type SetupOption interface { 27 | isSetupOption() 28 | } 29 | 30 | type ListenAddress string 31 | type Peer struct { 32 | URI string 33 | SourceInterface string 34 | } 35 | type NodeInfo map[string]interface{} 36 | type NodeInfoPrivacy bool 37 | type NetworkDomain struct { 38 | Prefix string 39 | } 40 | type AllowedPublicKey ed25519.PublicKey 41 | 42 | func (a ListenAddress) isSetupOption() {} 43 | func (a Peer) isSetupOption() {} 44 | func (a NodeInfo) isSetupOption() {} 45 | func (a NodeInfoPrivacy) isSetupOption() {} 46 | func (a NetworkDomain) isSetupOption() {} 47 | func (a AllowedPublicKey) isSetupOption() {} 48 | -------------------------------------------------------------------------------- /src/core/types.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "errors" 4 | 5 | // In-band packet types 6 | const ( 7 | typeSessionDummy = iota // nolint:deadcode,varcheck 8 | typeSessionTraffic 9 | typeSessionProto 10 | ) 11 | 12 | // Protocol packet types 13 | const ( 14 | typeProtoDummy = iota 15 | typeProtoNodeInfoRequest 16 | typeProtoNodeInfoResponse 17 | typeProtoDebug = 255 18 | ) 19 | 20 | var ErrTimeout = errors.New("Operation timeout") 21 | -------------------------------------------------------------------------------- /src/core/version.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // This file contains the version metadata struct 4 | // Used in the initial connection setup and key exchange 5 | // Some of this could arguably go in wire.go instead 6 | 7 | import "crypto/ed25519" 8 | 9 | // This is the version-specific metadata exchanged at the start of a connection. 10 | // It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. 11 | // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. 12 | type version_metadata struct { 13 | meta [4]byte 14 | ver uint8 // 1 byte in this version 15 | // Everything after this point potentially depends on the version number, and is subject to change in future versions 16 | minorVer uint8 // 1 byte in this version 17 | key ed25519.PublicKey 18 | } 19 | 20 | // Gets a base metadata with no keys set, but with the correct version numbers. 21 | func version_getBaseMetadata() version_metadata { 22 | return version_metadata{ 23 | meta: [4]byte{'m', 'e', 't', 'a'}, 24 | ver: 0, 25 | minorVer: 4, 26 | } 27 | } 28 | 29 | // Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. 30 | func version_getMetaLength() (mlen int) { 31 | mlen += 4 // meta 32 | mlen++ // ver, as long as it's < 127, which it is in this version 33 | mlen++ // minorVer, as long as it's < 127, which it is in this version 34 | mlen += ed25519.PublicKeySize // key 35 | return 36 | } 37 | 38 | // Encodes version metadata into its wire format. 39 | func (m *version_metadata) encode() []byte { 40 | bs := make([]byte, 0, version_getMetaLength()) 41 | bs = append(bs, m.meta[:]...) 42 | bs = append(bs, m.ver) 43 | bs = append(bs, m.minorVer) 44 | bs = append(bs, m.key[:]...) 45 | if len(bs) != version_getMetaLength() { 46 | panic("Inconsistent metadata length") 47 | } 48 | return bs 49 | } 50 | 51 | // Decodes version metadata from its wire format into the struct. 52 | func (m *version_metadata) decode(bs []byte) bool { 53 | if len(bs) != version_getMetaLength() { 54 | return false 55 | } 56 | offset := 0 57 | offset += copy(m.meta[:], bs[offset:]) 58 | m.ver, offset = bs[offset], offset+1 59 | m.minorVer, offset = bs[offset], offset+1 60 | m.key = append([]byte(nil), bs[offset:]...) 61 | return true 62 | } 63 | 64 | // Checks that the "meta" bytes and the version numbers are the expected values. 65 | func (m *version_metadata) check() bool { 66 | base := version_getBaseMetadata() 67 | return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer 68 | } 69 | -------------------------------------------------------------------------------- /src/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | 9 | "github.com/RiV-chain/RiV-mesh/src/config" 10 | "github.com/hjson/hjson-go" 11 | "github.com/mitchellh/mapstructure" 12 | "golang.org/x/text/encoding/unicode" 13 | ) 14 | 15 | type MulticastInterfaceConfig = config.MulticastInterfaceConfig 16 | type NetworkDomainConfig = config.NetworkDomainConfig 17 | 18 | var defaultConfig = "" // LDFLAGS='-X github.com/RiV-chain/RiV-mesh/src/defaults.defaultConfig=/path/to/config 19 | 20 | type defaultParameters struct { 21 | //Default Http address 22 | DefaultHttpAddress string 23 | 24 | //Public peers URL 25 | DefaultPublicPeersUrl string 26 | 27 | //Network domain 28 | DefaultNetworkDomain NetworkDomainConfig 29 | } 30 | 31 | // Defines which parameters are expected by default for configuration on a 32 | // specific platform. These values are populated in the relevant defaults_*.go 33 | // for the platform being targeted. They must be set. 34 | type platformDefaultParameters struct { 35 | 36 | // Configuration (used for meshctl) 37 | DefaultConfigFile string 38 | 39 | // Multicast interfaces 40 | DefaultMulticastInterfaces []MulticastInterfaceConfig 41 | 42 | // TUN 43 | MaximumIfMTU uint64 44 | DefaultIfMTU uint64 45 | DefaultIfName string 46 | } 47 | 48 | // Defines defaults for the all platforms. 49 | func Define() defaultParameters { 50 | return defaultParameters{ 51 | 52 | DefaultHttpAddress: "http://localhost:19019", 53 | 54 | DefaultPublicPeersUrl: "https://map.rivchain.org/rest/peers.json", 55 | 56 | // Network domain 57 | DefaultNetworkDomain: NetworkDomainConfig{ 58 | Prefix: "fc", 59 | }, 60 | } 61 | } 62 | 63 | func GetDefaults() platformDefaultParameters { 64 | defaults := getDefaults() 65 | if defaultConfig != "" { 66 | defaults.DefaultConfigFile = defaultConfig 67 | } 68 | return defaults 69 | } 70 | 71 | // Generates default configuration and returns a pointer to the resulting 72 | // NodeConfig. This is used when outputting the -genconf parameter and also when 73 | // using -autoconf. 74 | func GenerateConfig() *config.NodeConfig { 75 | // Get the defaults for the platform. 76 | defaults := GetDefaults() 77 | // Create a node configuration and populate it. 78 | cfg := new(config.NodeConfig) 79 | cfg.NewKeys() 80 | cfg.Listen = []string{} 81 | cfg.Peers = []string{} 82 | cfg.InterfacePeers = map[string][]string{} 83 | cfg.AllowedPublicKeys = []string{} 84 | cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces 85 | cfg.IfName = defaults.DefaultIfName 86 | cfg.IfMTU = defaults.DefaultIfMTU 87 | cfg.NodeInfoPrivacy = false 88 | cfg.HttpAddress = Define().DefaultHttpAddress 89 | cfg.NetworkDomain = Define().DefaultNetworkDomain 90 | cfg.PublicPeersUrl = Define().DefaultPublicPeersUrl 91 | 92 | return cfg 93 | } 94 | 95 | func Genconf(isjson bool) string { 96 | cfg := GenerateConfig() 97 | var bs []byte 98 | if isjson { 99 | bs, _ = json.MarshalIndent(cfg, "", " ") 100 | } else { 101 | bs, _ = hjson.Marshal(cfg) 102 | } 103 | return string(bs) 104 | } 105 | 106 | func ReadConfig(useconffile string) (*config.NodeConfig, error) { 107 | // Use a configuration file. If -useconf, the configuration will be read 108 | // from stdin. If -useconffile, the configuration will be read from the 109 | // filesystem. 110 | var conf []byte 111 | var err error 112 | if useconffile != "" { 113 | // Read the file from the filesystem 114 | conf, err = os.ReadFile(useconffile) 115 | } else { 116 | // Read the file from stdin. 117 | conf, err = io.ReadAll(os.Stdin) 118 | } 119 | if err != nil { 120 | return nil, err 121 | } 122 | // If there's a byte order mark - which Windows 10 is now incredibly fond of 123 | // throwing everywhere when it's converting things into UTF-16 for the hell 124 | // of it - remove it and decode back down into UTF-8. This is necessary 125 | // because hjson doesn't know what to do with UTF-16 and will panic 126 | if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || 127 | bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { 128 | utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) 129 | decoder := utf.NewDecoder() 130 | conf, err = decoder.Bytes(conf) 131 | if err != nil { 132 | return nil, err 133 | } 134 | } 135 | // Generate a new configuration - this gives us a set of sane defaults - 136 | // then parse the configuration we loaded above on top of it. The effect 137 | // of this is that any configuration item that is missing from the provided 138 | // configuration will use a sane default. 139 | cfg := GenerateConfig() 140 | var dat map[string]interface{} 141 | if err := hjson.Unmarshal(conf, &dat); err != nil { 142 | return nil, err 143 | } 144 | // Sanitise the config 145 | confJson, err := json.Marshal(dat) 146 | if err != nil { 147 | return nil, err 148 | } 149 | if err := json.Unmarshal(confJson, &cfg); err != nil { 150 | return nil, err 151 | } 152 | // Overlay our newly mapped configuration onto the autoconf node config that 153 | // we generated above. 154 | if err = mapstructure.Decode(dat, &cfg); err != nil { 155 | return nil, err 156 | } 157 | return cfg, nil 158 | } 159 | 160 | func WriteConfig(confFn string, cfg *config.NodeConfig) error { 161 | bs, err := hjson.Marshal(cfg) 162 | if err != nil { 163 | return err 164 | } 165 | err = os.WriteFile(confFn, bs, 0644) 166 | if err != nil { 167 | return err 168 | } 169 | return nil 170 | } 171 | 172 | func GetHttpEndpoint(defaultEndpoint string) string { 173 | if config, err := ReadConfig(GetDefaults().DefaultConfigFile); err == nil { 174 | if ep := config.HttpAddress; ep != "none" && ep != "" { 175 | return ep 176 | } 177 | } 178 | return defaultEndpoint 179 | } 180 | -------------------------------------------------------------------------------- /src/defaults/defaults_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package defaults 5 | 6 | // Sane defaults for the macOS/Darwin platform. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "/etc/mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: "en.*", Beacon: true, Listen: true}, 17 | {Regex: "bridge.*", Beacon: true, Listen: true}, 18 | }, 19 | 20 | // TUN 21 | MaximumIfMTU: 65535, 22 | DefaultIfMTU: 65535, 23 | DefaultIfName: "auto", 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/defaults/defaults_freebsd.go: -------------------------------------------------------------------------------- 1 | //go:build freebsd 2 | // +build freebsd 3 | 4 | package defaults 5 | 6 | // Sane defaults for the BSD platforms. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "/usr/local/etc/mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: ".*", Beacon: true, Listen: true}, 17 | }, 18 | 19 | // TUN 20 | MaximumIfMTU: 32767, 21 | DefaultIfMTU: 32767, 22 | DefaultIfName: "/dev/tun0", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/defaults/defaults_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package defaults 5 | 6 | // Sane defaults for the Linux platform. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "/etc/mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: ".*", Beacon: true, Listen: true}, 17 | }, 18 | 19 | // TUN 20 | MaximumIfMTU: 65535, 21 | DefaultIfMTU: 65535, 22 | DefaultIfName: "auto", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/defaults/defaults_openbsd.go: -------------------------------------------------------------------------------- 1 | //go:build openbsd 2 | // +build openbsd 3 | 4 | package defaults 5 | 6 | // Sane defaults for the BSD platforms. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "/etc/mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: ".*", Beacon: true, Listen: true}, 17 | }, 18 | 19 | // TUN 20 | MaximumIfMTU: 16384, 21 | DefaultIfMTU: 16384, 22 | DefaultIfName: "tun0", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/defaults/defaults_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !windows && !openbsd && !freebsd 2 | // +build !linux,!darwin,!windows,!openbsd,!freebsd 3 | 4 | package defaults 5 | 6 | // Sane defaults for the other platforms. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "/etc/mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: ".*", Beacon: true, Listen: true}, 17 | }, 18 | 19 | // TUN 20 | MaximumIfMTU: 65535, 21 | DefaultIfMTU: 65535, 22 | DefaultIfName: "none", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/defaults/defaults_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package defaults 5 | 6 | // Sane defaults for the Windows platform. The "default" options may be 7 | // may be replaced by the running configuration. 8 | func getDefaults() platformDefaultParameters { 9 | return platformDefaultParameters{ 10 | 11 | // Configuration (used for meshctl) 12 | DefaultConfigFile: "C:\\ProgramData\\RiV-mesh\\mesh.conf", 13 | 14 | // Multicast interfaces 15 | DefaultMulticastInterfaces: []MulticastInterfaceConfig{ 16 | {Regex: ".*", Beacon: true, Listen: true}, 17 | }, 18 | 19 | // TUN 20 | MaximumIfMTU: 65535, 21 | DefaultIfMTU: 65535, 22 | DefaultIfName: "RiV-mesh", 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ipv6rwc/icmpv6.go: -------------------------------------------------------------------------------- 1 | package ipv6rwc 2 | 3 | // The ICMPv6 module implements functions to easily create ICMPv6 4 | // packets. These functions, when mixed with the built-in Go IPv6 5 | // and ICMP libraries, can be used to send control messages back 6 | // to the host. Examples include: 7 | // - NDP messages, when running in TAP mode 8 | // - Packet Too Big messages, when packets exceed the session MTU 9 | // - Destination Unreachable messages, when a session prohibits 10 | // incoming traffic 11 | 12 | import ( 13 | "encoding/binary" 14 | "net" 15 | 16 | "golang.org/x/net/icmp" 17 | "golang.org/x/net/ipv6" 18 | ) 19 | 20 | type ICMPv6 struct{} 21 | 22 | // Marshal returns the binary encoding of h. 23 | func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { 24 | b := make([]byte, 40) 25 | b[0] |= byte(h.Version) << 4 26 | b[0] |= byte(h.TrafficClass) >> 4 27 | b[1] |= byte(h.TrafficClass) << 4 28 | b[1] |= byte(h.FlowLabel >> 16) 29 | b[2] = byte(h.FlowLabel >> 8) 30 | b[3] = byte(h.FlowLabel) 31 | binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen)) 32 | b[6] = byte(h.NextHeader) 33 | b[7] = byte(h.HopLimit) 34 | copy(b[8:24], h.Src) 35 | copy(b[24:40], h.Dst) 36 | return b, nil 37 | } 38 | 39 | // Creates an ICMPv6 packet based on the given icmp.MessageBody and other 40 | // parameters, complete with IP headers only, which can be written directly to 41 | // a TUN adapter, or called directly by the CreateICMPv6L2 function when 42 | // generating a message for TAP adapters. 43 | func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { 44 | // Create the ICMPv6 message 45 | icmpMessage := icmp.Message{ 46 | Type: mtype, 47 | Code: mcode, 48 | Body: mbody, 49 | } 50 | 51 | // Convert the ICMPv6 message into []byte 52 | icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(src, dst)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // Create the IPv6 header 58 | ipv6Header := ipv6.Header{ 59 | Version: ipv6.Version, 60 | NextHeader: 58, 61 | PayloadLen: len(icmpMessageBuf), 62 | HopLimit: 255, 63 | Src: src, 64 | Dst: dst, 65 | } 66 | 67 | // Convert the IPv6 header into []byte 68 | ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | // Construct the packet 74 | responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen) 75 | copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf) 76 | copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf) 77 | 78 | // Send it back 79 | return responsePacket, nil 80 | } 81 | -------------------------------------------------------------------------------- /src/ipv6rwc/ipv6rwc.go: -------------------------------------------------------------------------------- 1 | package ipv6rwc 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "golang.org/x/net/icmp" 12 | "golang.org/x/net/ipv6" 13 | 14 | iwt "github.com/Arceliar/ironwood/types" 15 | 16 | //"github.com/RiV-chain/RiV-mesh/src/address" 17 | "github.com/RiV-chain/RiV-mesh/src/core" 18 | ) 19 | 20 | const keyStoreTimeout = 2 * time.Minute 21 | 22 | // Out-of-band packet types 23 | const ( 24 | typeKeyDummy = iota // nolint:deadcode,varcheck 25 | typeKeyLookup 26 | typeKeyResponse 27 | ) 28 | 29 | type keyArray [ed25519.PublicKeySize]byte 30 | 31 | type keyStore struct { 32 | core *core.Core 33 | address core.Address 34 | subnet core.Subnet 35 | mutex sync.Mutex 36 | keyToInfo map[keyArray]*keyInfo 37 | addrToInfo map[core.Address]*keyInfo 38 | addrBuffer map[core.Address]*buffer 39 | subnetToInfo map[core.Subnet]*keyInfo 40 | subnetBuffer map[core.Subnet]*buffer 41 | mtu uint64 42 | } 43 | 44 | type keyInfo struct { 45 | key keyArray 46 | address core.Address 47 | subnet core.Subnet 48 | timeout *time.Timer // From calling a time.AfterFunc to do cleanup 49 | } 50 | 51 | type buffer struct { 52 | packet []byte 53 | timeout *time.Timer 54 | } 55 | 56 | func (k *keyStore) init(c *core.Core) { 57 | k.core = c 58 | k.address = *c.AddrForKey(k.core.PublicKey()) 59 | k.subnet = *c.SubnetForKey(k.core.PublicKey()) 60 | if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil { 61 | err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) 62 | panic(err) 63 | } 64 | k.keyToInfo = make(map[keyArray]*keyInfo) 65 | k.addrToInfo = make(map[core.Address]*keyInfo) 66 | k.addrBuffer = make(map[core.Address]*buffer) 67 | k.subnetToInfo = make(map[core.Subnet]*keyInfo) 68 | k.subnetBuffer = make(map[core.Subnet]*buffer) 69 | k.mtu = 1280 // Default to something safe, expect user to set this 70 | } 71 | 72 | func (k *keyStore) sendToAddress(addr core.Address, bs []byte) { 73 | k.mutex.Lock() 74 | if info := k.addrToInfo[addr]; info != nil { 75 | k.resetTimeout(info) 76 | k.mutex.Unlock() 77 | _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) 78 | } else { 79 | var buf *buffer 80 | if buf = k.addrBuffer[addr]; buf == nil { 81 | buf = new(buffer) 82 | k.addrBuffer[addr] = buf 83 | } 84 | msg := append([]byte(nil), bs...) 85 | buf.packet = msg 86 | if buf.timeout != nil { 87 | buf.timeout.Stop() 88 | } 89 | buf.timeout = time.AfterFunc(keyStoreTimeout, func() { 90 | k.mutex.Lock() 91 | defer k.mutex.Unlock() 92 | if nbuf := k.addrBuffer[addr]; nbuf == buf { 93 | delete(k.addrBuffer, addr) 94 | } 95 | }) 96 | k.mutex.Unlock() 97 | k.sendKeyLookup(k.core.GetAddressKey(addr)) 98 | } 99 | } 100 | 101 | func (k *keyStore) sendToSubnet(subnet core.Subnet, bs []byte) { 102 | k.mutex.Lock() 103 | if info := k.subnetToInfo[subnet]; info != nil { 104 | k.resetTimeout(info) 105 | k.mutex.Unlock() 106 | _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) 107 | } else { 108 | var buf *buffer 109 | if buf = k.subnetBuffer[subnet]; buf == nil { 110 | buf = new(buffer) 111 | k.subnetBuffer[subnet] = buf 112 | } 113 | msg := append([]byte(nil), bs...) 114 | buf.packet = msg 115 | if buf.timeout != nil { 116 | buf.timeout.Stop() 117 | } 118 | buf.timeout = time.AfterFunc(keyStoreTimeout, func() { 119 | k.mutex.Lock() 120 | defer k.mutex.Unlock() 121 | if nbuf := k.subnetBuffer[subnet]; nbuf == buf { 122 | delete(k.subnetBuffer, subnet) 123 | } 124 | }) 125 | k.mutex.Unlock() 126 | k.sendKeyLookup(k.core.GetSubnetKey(subnet)) 127 | } 128 | } 129 | 130 | func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { 131 | k.mutex.Lock() 132 | var kArray keyArray 133 | copy(kArray[:], key) 134 | var info *keyInfo 135 | var packets [][]byte 136 | if info = k.keyToInfo[kArray]; info == nil { 137 | info = new(keyInfo) 138 | info.key = kArray 139 | info.address = *k.core.AddrForKey(ed25519.PublicKey(info.key[:])) 140 | info.subnet = *k.core.SubnetForKey(ed25519.PublicKey(info.key[:])) 141 | k.keyToInfo[info.key] = info 142 | k.addrToInfo[info.address] = info 143 | k.subnetToInfo[info.subnet] = info 144 | if buf := k.addrBuffer[info.address]; buf != nil { 145 | packets = append(packets, buf.packet) 146 | delete(k.addrBuffer, info.address) 147 | } 148 | if buf := k.subnetBuffer[info.subnet]; buf != nil { 149 | packets = append(packets, buf.packet) 150 | delete(k.subnetBuffer, info.subnet) 151 | } 152 | } 153 | k.resetTimeout(info) 154 | k.mutex.Unlock() 155 | for _, packet := range packets { 156 | _, _ = k.core.WriteTo(packet, iwt.Addr(info.key[:])) 157 | } 158 | return info 159 | } 160 | 161 | func (k *keyStore) resetTimeout(info *keyInfo) { 162 | if info.timeout != nil { 163 | info.timeout.Stop() 164 | } 165 | info.timeout = time.AfterFunc(keyStoreTimeout, func() { 166 | k.mutex.Lock() 167 | defer k.mutex.Unlock() 168 | if nfo := k.keyToInfo[info.key]; nfo == info { 169 | delete(k.keyToInfo, info.key) 170 | } 171 | if nfo := k.addrToInfo[info.address]; nfo == info { 172 | delete(k.addrToInfo, info.address) 173 | } 174 | if nfo := k.subnetToInfo[info.subnet]; nfo == info { 175 | delete(k.subnetToInfo, info.subnet) 176 | } 177 | }) 178 | } 179 | 180 | func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { 181 | if len(data) != 1+ed25519.SignatureSize { 182 | return 183 | } 184 | sig := data[1:] 185 | switch data[0] { 186 | case typeKeyLookup: 187 | snet := *k.core.SubnetForKey(toKey) 188 | if snet == k.subnet && ed25519.Verify(fromKey, toKey[:], sig) { 189 | // This is looking for at least our subnet (possibly our address) 190 | // Send a response 191 | k.sendKeyResponse(fromKey) 192 | } 193 | case typeKeyResponse: 194 | // TODO keep a list of something to match against... 195 | // Ignore the response if it doesn't match anything of interest... 196 | if ed25519.Verify(fromKey, toKey[:], sig) { 197 | k.update(fromKey) 198 | } 199 | } 200 | } 201 | 202 | func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) { 203 | sig := ed25519.Sign(k.core.PrivateKey(), partial[:]) 204 | bs := append([]byte{typeKeyLookup}, sig...) 205 | _ = k.core.SendOutOfBand(partial, bs) 206 | } 207 | 208 | func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { 209 | sig := ed25519.Sign(k.core.PrivateKey(), dest[:]) 210 | bs := append([]byte{typeKeyResponse}, sig...) 211 | _ = k.core.SendOutOfBand(dest, bs) 212 | } 213 | 214 | func (k *keyStore) readPC(p []byte) (int, error) { 215 | buf := make([]byte, k.core.MTU(), 65535) 216 | for { 217 | bs := buf 218 | n, from, err := k.core.ReadFrom(bs) 219 | if err != nil { 220 | return n, err 221 | } 222 | if n == 0 { 223 | continue 224 | } 225 | bs = bs[:n] 226 | if len(bs) == 0 { 227 | continue 228 | } 229 | if bs[0]&0xf0 != 0x60 { 230 | continue // not IPv6 231 | } 232 | if len(bs) < 40 { 233 | continue 234 | } 235 | k.mutex.Lock() 236 | mtu := int(k.mtu) 237 | k.mutex.Unlock() 238 | if len(bs) > mtu { 239 | // Using bs would make it leak off the stack, so copy to buf 240 | buf := make([]byte, 512) 241 | cn := copy(buf, bs) 242 | ptb := &icmp.PacketTooBig{ 243 | MTU: mtu, 244 | Data: buf[:cn], 245 | } 246 | if packet, err := CreateICMPv6(buf[8:24], buf[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { 247 | _, _ = k.writePC(packet) 248 | } 249 | continue 250 | } 251 | var srcAddr, dstAddr core.Address 252 | var srcSubnet, dstSubnet core.Subnet 253 | copy(srcAddr[:], bs[8:]) 254 | copy(dstAddr[:], bs[24:]) 255 | copy(srcSubnet[:], bs[8:]) 256 | copy(dstSubnet[:], bs[24:]) 257 | if dstAddr != k.address && dstSubnet != k.subnet { 258 | continue // bad local address/subnet 259 | } 260 | info := k.update(ed25519.PublicKey(from.(iwt.Addr))) 261 | if srcAddr != info.address && srcSubnet != info.subnet { 262 | continue // bad remote address/subnet 263 | } 264 | n = copy(p, bs) 265 | return n, nil 266 | } 267 | } 268 | 269 | func (k *keyStore) writePC(bs []byte) (int, error) { 270 | if bs[0]&0xf0 != 0x60 { 271 | return 0, errors.New("not an IPv6 packet") // not IPv6 272 | } 273 | if len(bs) < 40 { 274 | strErr := fmt.Sprint("undersized IPv6 packet, length: ", len(bs)) 275 | return 0, errors.New(strErr) 276 | } 277 | var srcAddr, dstAddr core.Address 278 | var srcSubnet, dstSubnet core.Subnet 279 | copy(srcAddr[:], bs[8:]) 280 | copy(dstAddr[:], bs[24:]) 281 | copy(srcSubnet[:], bs[8:]) 282 | copy(dstSubnet[:], bs[24:]) 283 | if srcAddr != k.address && srcSubnet != k.subnet { 284 | // This happens all the time due to link-local traffic 285 | // Don't send back an error, just drop it 286 | strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String()) 287 | return 0, errors.New(strErr) 288 | } 289 | if k.core.IsValidAddress(dstAddr) { 290 | k.sendToAddress(dstAddr, bs) 291 | } else if k.core.IsValidSubnet(dstSubnet) { 292 | k.sendToSubnet(dstSubnet, bs) 293 | } else { 294 | return 0, errors.New("invalid destination address") 295 | } 296 | return len(bs), nil 297 | } 298 | 299 | // Exported API 300 | 301 | func (k *keyStore) MaxMTU() uint64 { 302 | return k.core.MTU() 303 | } 304 | 305 | func (k *keyStore) SetMTU(mtu uint64) { 306 | if mtu > k.MaxMTU() { 307 | mtu = k.MaxMTU() 308 | } 309 | if mtu < 1280 { 310 | mtu = 1280 311 | } 312 | k.mutex.Lock() 313 | k.mtu = mtu 314 | k.mutex.Unlock() 315 | } 316 | 317 | func (k *keyStore) MTU() uint64 { 318 | k.mutex.Lock() 319 | mtu := k.mtu 320 | k.mutex.Unlock() 321 | return mtu 322 | } 323 | 324 | type ReadWriteCloser struct { 325 | keyStore 326 | } 327 | 328 | func NewReadWriteCloser(c *core.Core) *ReadWriteCloser { 329 | rwc := new(ReadWriteCloser) 330 | rwc.init(c) 331 | return rwc 332 | } 333 | 334 | func (rwc *ReadWriteCloser) Address() core.Address { 335 | return rwc.address 336 | } 337 | 338 | func (rwc *ReadWriteCloser) Subnet() core.Subnet { 339 | return rwc.subnet 340 | } 341 | 342 | func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) { 343 | return rwc.readPC(p) 344 | } 345 | 346 | func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) { 347 | return rwc.writePC(p) 348 | } 349 | 350 | func (rwc *ReadWriteCloser) Close() error { 351 | err := rwc.core.Close() 352 | rwc.core.Stop() 353 | return err 354 | } 355 | -------------------------------------------------------------------------------- /src/multicast/admin.go: -------------------------------------------------------------------------------- 1 | package multicast 2 | 3 | type GetMulticastInterfacesRequest struct{} 4 | type GetMulticastInterfacesResponse struct { 5 | Interfaces []string `json:"multicast_interfaces"` 6 | } 7 | 8 | func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error { 9 | res.Interfaces = []string{} 10 | for _, v := range m.Interfaces() { 11 | res.Interfaces = append(res.Interfaces, v.Name) 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /src/multicast/multicast_android.go: -------------------------------------------------------------------------------- 1 | //go:build android 2 | // +build android 3 | 4 | package multicast 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "syscall" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | func (m *Multicast) _multicastStarted() { 15 | 16 | } 17 | 18 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 19 | var control error 20 | var reuseaddr error 21 | 22 | control = c.Control(func(fd uintptr) { 23 | // Previously we used SO_REUSEPORT here, but that meant that machines running 24 | // RiV-mesh nodes as different users would inevitably fail with EADDRINUSE. 25 | // The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR 26 | // instead. 27 | if reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); reuseaddr != nil { 28 | fmt.Fprintf(os.Stderr, "Failed to set SO_REUSEADDR on socket: %s\n", reuseaddr) 29 | } 30 | }) 31 | return control 32 | } 33 | -------------------------------------------------------------------------------- /src/multicast/multicast_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo && (darwin || ios) 2 | // +build !cgo 3 | // +build darwin ios 4 | 5 | package multicast 6 | 7 | import ( 8 | "syscall" 9 | 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | func (m *Multicast) _multicastStarted() { 14 | 15 | } 16 | 17 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 18 | var control error 19 | var reuseport error 20 | var recvanyif error 21 | 22 | control = c.Control(func(fd uintptr) { 23 | reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 24 | 25 | // sys/socket.h: #define SO_RECV_ANYIF 0x1104 26 | recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) 27 | }) 28 | 29 | switch { 30 | case reuseport != nil: 31 | return reuseport 32 | case recvanyif != nil: 33 | return recvanyif 34 | default: 35 | return control 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/multicast/multicast_darwin_cgo.go: -------------------------------------------------------------------------------- 1 | //go:build (darwin && cgo) || (ios && cgo) 2 | // +build darwin,cgo ios,cgo 3 | 4 | package multicast 5 | 6 | /* 7 | #cgo CFLAGS: -x objective-c 8 | #cgo LDFLAGS: -framework Foundation 9 | #import 10 | NSNetServiceBrowser *serviceBrowser; 11 | void StartAWDLBrowsing() { 12 | if (serviceBrowser == nil) { 13 | serviceBrowser = [[NSNetServiceBrowser alloc] init]; 14 | serviceBrowser.includesPeerToPeer = YES; 15 | } 16 | [serviceBrowser searchForServicesOfType:@"_mesh._tcp" inDomain:@""]; 17 | } 18 | void StopAWDLBrowsing() { 19 | if (serviceBrowser == nil) { 20 | return; 21 | } 22 | [serviceBrowser stop]; 23 | } 24 | */ 25 | import "C" 26 | import ( 27 | "syscall" 28 | "time" 29 | 30 | "golang.org/x/sys/unix" 31 | ) 32 | 33 | func (m *Multicast) _multicastStarted() { 34 | if !m._isOpen { 35 | return 36 | } 37 | C.StopAWDLBrowsing() 38 | for _, info := range m._interfaces { 39 | if info.iface.Name == "awdl0" { 40 | C.StartAWDLBrowsing() 41 | break 42 | } 43 | } 44 | time.AfterFunc(time.Minute, func() { 45 | m.Act(nil, m._multicastStarted) 46 | }) 47 | } 48 | 49 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 50 | var control error 51 | var reuseport error 52 | var recvanyif error 53 | 54 | control = c.Control(func(fd uintptr) { 55 | reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 56 | 57 | // sys/socket.h: #define SO_RECV_ANYIF 0x1104 58 | recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) 59 | }) 60 | 61 | switch { 62 | case reuseport != nil: 63 | return reuseport 64 | case recvanyif != nil: 65 | return recvanyif 66 | default: 67 | return control 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/multicast/multicast_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !ios && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows 2 | // +build !linux,!darwin,!ios,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows 3 | 4 | package multicast 5 | 6 | import "syscall" 7 | 8 | func (m *Multicast) _multicastStarted() { 9 | 10 | } 11 | 12 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /src/multicast/multicast_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !android && (linux || netbsd || freebsd || openbsd || dragonflybsd) 2 | // +build !android 3 | // +build linux netbsd freebsd openbsd dragonflybsd 4 | 5 | package multicast 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "syscall" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (m *Multicast) _multicastStarted() { 16 | 17 | } 18 | 19 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 20 | var control error 21 | var reuseaddr error 22 | 23 | control = c.Control(func(fd uintptr) { 24 | // Previously we used SO_REUSEPORT here, but that meant that machines running 25 | // RiV-mesh nodes as different users would inevitably fail with EADDRINUSE. 26 | // The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR 27 | // instead. 28 | if reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); reuseaddr != nil { 29 | fmt.Fprintf(os.Stderr, "Failed to set SO_REUSEADDR on socket: %s\n", reuseaddr) 30 | } 31 | }) 32 | return control 33 | } 34 | -------------------------------------------------------------------------------- /src/multicast/multicast_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package multicast 5 | 6 | import ( 7 | "syscall" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | func (m *Multicast) _multicastStarted() { 13 | 14 | } 15 | 16 | func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { 17 | var control error 18 | var reuseaddr error 19 | 20 | control = c.Control(func(fd uintptr) { 21 | reuseaddr = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) 22 | }) 23 | 24 | switch { 25 | case reuseaddr != nil: 26 | return reuseaddr 27 | default: 28 | return control 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/multicast/options.go: -------------------------------------------------------------------------------- 1 | package multicast 2 | 3 | import "regexp" 4 | 5 | func (m *Multicast) _applyOption(opt SetupOption) { 6 | switch v := opt.(type) { 7 | case MulticastInterface: 8 | m.config._interfaces[v] = struct{}{} 9 | case GroupAddress: 10 | m.config._groupAddr = v 11 | } 12 | } 13 | 14 | type SetupOption interface { 15 | isSetupOption() 16 | } 17 | 18 | type MulticastInterface struct { 19 | Regex *regexp.Regexp 20 | Beacon bool 21 | Listen bool 22 | Port uint16 23 | Priority uint8 24 | } 25 | 26 | type GroupAddress string 27 | 28 | func (a MulticastInterface) isSetupOption() {} 29 | func (a GroupAddress) isSetupOption() {} 30 | -------------------------------------------------------------------------------- /src/restapi/IP2LOCATION-LITE-DB1.BIN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiV-chain/RiV-mesh/fd27d0bfec6fd047add41f1a6698969f5d4a9cf8/src/restapi/IP2LOCATION-LITE-DB1.BIN -------------------------------------------------------------------------------- /src/tun/admin.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | type GetTUNRequest struct{} 4 | type GetTUNResponse struct { 5 | Enabled bool `json:"enabled"` 6 | Name string `json:"name,omitempty"` 7 | MTU uint64 `json:"mtu,omitempty"` 8 | } 9 | 10 | type TUNEntry struct { 11 | MTU uint64 `json:"mtu"` 12 | } 13 | 14 | func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { 15 | res.Enabled = t.isEnabled 16 | if !t.isEnabled { 17 | return nil 18 | } 19 | res.Name = t.Name() 20 | res.MTU = t.MTU() 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /src/tun/iface.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | const TUN_OFFSET_BYTES = 4 4 | 5 | func (tun *TunAdapter) read() { 6 | var buf [TUN_OFFSET_BYTES + 65535]byte 7 | for { 8 | n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES) 9 | if n <= TUN_OFFSET_BYTES || err != nil { 10 | tun.log.Errorln("Error reading TUN:", err) 11 | ferr := tun.iface.Flush() 12 | if ferr != nil { 13 | tun.log.Errorln("Unable to flush packets:", ferr) 14 | } 15 | return 16 | } 17 | begin := TUN_OFFSET_BYTES 18 | end := begin + n 19 | bs := buf[begin:end] 20 | if _, err := tun.rwc.Write(bs); err != nil { 21 | tun.log.Debugln("Unable to send packet:", err) 22 | } 23 | } 24 | } 25 | 26 | func (tun *TunAdapter) write() { 27 | var buf [TUN_OFFSET_BYTES + 65535]byte 28 | for { 29 | bs := buf[TUN_OFFSET_BYTES:] 30 | n, err := tun.rwc.Read(bs) 31 | if err != nil { 32 | tun.log.Errorln("Exiting tun writer due to core read error:", err) 33 | return 34 | } 35 | if !tun.isEnabled { 36 | continue // Nothing to do, the tun isn't enabled 37 | } 38 | bs = buf[:TUN_OFFSET_BYTES+n] 39 | if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil { 40 | tun.Act(nil, func() { 41 | if !tun.isOpen { 42 | tun.log.Errorln("TUN iface write error:", err) 43 | } 44 | }) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tun/options.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | func (m *TunAdapter) _applyOption(opt SetupOption) { 4 | switch v := opt.(type) { 5 | case InterfaceName: 6 | m.config.name = v 7 | case InterfaceMTU: 8 | m.config.mtu = v 9 | } 10 | } 11 | 12 | type SetupOption interface { 13 | isSetupOption() 14 | } 15 | 16 | type InterfaceName string 17 | type InterfaceMTU uint64 18 | 19 | func (a InterfaceName) isSetupOption() {} 20 | func (a InterfaceMTU) isSetupOption() {} 21 | -------------------------------------------------------------------------------- /src/tun/tun.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | // This manages the tun driver to send/recv packets to/from applications 4 | 5 | // TODO: Connection timeouts (call Conn.Close() when we want to time out) 6 | // TODO: Don't block in reader on writes that are pending searches 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "net" 12 | 13 | "github.com/Arceliar/phony" 14 | "golang.zx2c4.com/wireguard/tun" 15 | 16 | //"github.com/RiV-chain/RiV-mesh/src/address" 17 | "github.com/RiV-chain/RiV-mesh/src/core" 18 | "github.com/RiV-chain/RiV-mesh/src/defaults" 19 | "github.com/RiV-chain/RiV-mesh/src/ipv6rwc" 20 | ) 21 | 22 | type MTU uint16 23 | 24 | // TunAdapter represents a running TUN interface and extends the 25 | // mesh.Adapter type. In order to use the TUN adapter with Mesh, you 26 | // should pass this object to the mesh.SetRouterAdapter() function before 27 | // calling mesh.Start(). 28 | type TunAdapter struct { 29 | core *core.Core 30 | rwc *ipv6rwc.ReadWriteCloser 31 | log core.Logger 32 | addr core.Address 33 | subnet core.Subnet 34 | mtu uint64 35 | iface tun.Device 36 | phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below 37 | //mutex sync.RWMutex // Protects the below 38 | isOpen bool 39 | isEnabled bool // Used by the writer to drop sessionTraffic if not enabled 40 | config struct { 41 | name InterfaceName 42 | mtu InterfaceMTU 43 | } 44 | } 45 | 46 | // Gets the maximum supported MTU for the platform based on the defaults in 47 | // defaults.GetDefaults(). 48 | func getSupportedMTU(mtu uint64) uint64 { 49 | if mtu < 1280 { 50 | return 1280 51 | } 52 | if mtu > MaximumMTU() { 53 | return MaximumMTU() 54 | } 55 | return mtu 56 | } 57 | 58 | // Name returns the name of the adapter, e.g. "tun0". On Windows, this may 59 | // return a canonical adapter name instead. 60 | func (tun *TunAdapter) Name() string { 61 | if name, err := tun.iface.Name(); err == nil { 62 | return name 63 | } 64 | return "" 65 | } 66 | 67 | // MTU gets the adapter's MTU. This can range between 1280 and 65535, although 68 | // the maximum value is determined by your platform. The returned value will 69 | // never exceed that of MaximumMTU(). 70 | func (tun *TunAdapter) MTU() uint64 { 71 | return getSupportedMTU(tun.mtu) 72 | } 73 | 74 | // DefaultName gets the default TUN interface name for your platform. 75 | func DefaultName() string { 76 | return defaults.GetDefaults().DefaultIfName 77 | } 78 | 79 | // DefaultMTU gets the default TUN interface MTU for your platform. This can 80 | // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. 81 | func DefaultMTU() uint64 { 82 | return defaults.GetDefaults().DefaultIfMTU 83 | } 84 | 85 | // MaximumMTU returns the maximum supported TUN interface MTU for your 86 | // platform. This can be as high as 65535, depending on platform, but is never 87 | // lower than 1280. 88 | func MaximumMTU() uint64 { 89 | return defaults.GetDefaults().MaximumIfMTU 90 | } 91 | 92 | // Init initialises the TUN module. You must have acquired a Listener from 93 | // the Mesh core before this point and it must not be in use elsewhere. 94 | func New(core *core.Core, log core.Logger, opts ...SetupOption) (*TunAdapter, error) { 95 | tun := &TunAdapter{ 96 | core: core, 97 | rwc: ipv6rwc.NewReadWriteCloser(core), 98 | log: log, 99 | } 100 | for _, opt := range opts { 101 | tun._applyOption(opt) 102 | } 103 | return tun, tun._start() 104 | } 105 | 106 | // Start the setup process for the TUN adapter. If successful, starts the 107 | // read/write goroutines to handle packets on that interface. 108 | func (tun *TunAdapter) _start() error { 109 | if tun.isOpen { 110 | return errors.New("TUN module is already started") 111 | } 112 | tun.addr = tun.rwc.Address() 113 | tun.subnet = tun.rwc.Subnet() 114 | addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(tun.core.GetPrefix())-1) 115 | if tun.config.name == "none" || tun.config.name == "dummy" { 116 | tun.log.Debugln("Not starting TUN as ifname is none or dummy") 117 | tun.isEnabled = false 118 | go tun.write() 119 | return nil 120 | } 121 | mtu := uint64(tun.config.mtu) 122 | if tun.rwc.MaxMTU() < mtu { 123 | mtu = tun.rwc.MaxMTU() 124 | } 125 | if err := tun.setup(string(tun.config.name), addr, mtu); err != nil { 126 | return err 127 | } 128 | if tun.MTU() != mtu { 129 | tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.mtu, tun.MTU(), MaximumMTU()) 130 | } 131 | tun.rwc.SetMTU(tun.MTU()) 132 | tun.isOpen = true 133 | tun.isEnabled = true 134 | go tun.read() 135 | go tun.write() 136 | return nil 137 | } 138 | 139 | // IsStarted returns true if the module has been started. 140 | func (tun *TunAdapter) IsStarted() bool { 141 | var isOpen bool 142 | phony.Block(tun, func() { 143 | isOpen = tun.isOpen 144 | }) 145 | return isOpen 146 | } 147 | 148 | func (tun *TunAdapter) Stop() error { 149 | var err error 150 | phony.Block(tun, func() { 151 | err = tun._stop() 152 | }) 153 | return err 154 | } 155 | 156 | func (tun *TunAdapter) _stop() error { 157 | tun.isOpen = false 158 | // by TUN, e.g. readers/writers, sessions 159 | if tun.iface != nil { 160 | // Just in case we failed to start up the iface for some reason, this can apparently happen on Windows 161 | tun.iface.Close() 162 | } 163 | return nil 164 | } 165 | -------------------------------------------------------------------------------- /src/tun/tun_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build openbsd || freebsd 2 | // +build openbsd freebsd 3 | 4 | package tun 5 | 6 | import ( 7 | "encoding/binary" 8 | "os/exec" 9 | "strconv" 10 | "strings" 11 | "syscall" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/unix" 15 | 16 | wgtun "golang.zx2c4.com/wireguard/tun" 17 | ) 18 | 19 | const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12 20 | 21 | type in6_addrlifetime struct { 22 | ia6t_expire float64 23 | ia6t_preferred float64 24 | ia6t_vltime uint32 25 | ia6t_pltime uint32 26 | } 27 | 28 | type sockaddr_in6 struct { 29 | sin6_len uint8 30 | sin6_family uint8 31 | sin6_port uint8 32 | sin6_flowinfo uint32 33 | sin6_addr [8]uint16 34 | sin6_scope_id uint32 35 | } 36 | 37 | /* 38 | from 39 | struct in6_ifreq { 40 | 277 char ifr_name[IFNAMSIZ]; 41 | 278 union { 42 | 279 struct sockaddr_in6 ifru_addr; 43 | 280 struct sockaddr_in6 ifru_dstaddr; 44 | 281 int ifru_flags; 45 | 282 int ifru_flags6; 46 | 283 int ifru_metric; 47 | 284 caddr_t ifru_data; 48 | 285 struct in6_addrlifetime ifru_lifetime; 49 | 286 struct in6_ifstat ifru_stat; 50 | 287 struct icmp6_ifstat ifru_icmp6stat; 51 | 288 u_int32_t ifru_scope_id[16]; 52 | 289 } ifr_ifru; 53 | 290 }; 54 | */ 55 | 56 | type in6_ifreq_mtu struct { 57 | ifr_name [syscall.IFNAMSIZ]byte 58 | ifru_mtu int 59 | } 60 | 61 | type in6_ifreq_addr struct { 62 | ifr_name [syscall.IFNAMSIZ]byte 63 | ifru_addr sockaddr_in6 64 | } 65 | 66 | type in6_ifreq_flags struct { 67 | ifr_name [syscall.IFNAMSIZ]byte 68 | flags int 69 | } 70 | 71 | type in6_ifreq_lifetime struct { 72 | ifr_name [syscall.IFNAMSIZ]byte 73 | ifru_addrlifetime in6_addrlifetime 74 | } 75 | 76 | // Configures the TUN adapter with the correct IPv6 address and MTU. 77 | func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { 78 | iface, err := wgtun.CreateTUN(ifname, int(mtu)) 79 | if err != nil { 80 | panic(err) 81 | } 82 | tun.iface = iface 83 | if mtu, err := iface.MTU(); err == nil { 84 | tun.mtu = getSupportedMTU(uint64(mtu)) 85 | } else { 86 | tun.mtu = 0 87 | } 88 | return tun.setupAddress(addr) 89 | } 90 | 91 | func (tun *TunAdapter) setupAddress(addr string) error { 92 | var sfd int 93 | var err error 94 | 95 | // Create system socket 96 | if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { 97 | tun.log.Printf("Create AF_INET socket failed: %v.", err) 98 | return err 99 | } 100 | 101 | // Friendly output 102 | tun.log.Infof("Interface name: %s", tun.Name()) 103 | tun.log.Infof("Interface IPv6: %s", addr) 104 | tun.log.Infof("Interface MTU: %d", tun.mtu) 105 | 106 | // Create the MTU request 107 | var ir in6_ifreq_mtu 108 | copy(ir.ifr_name[:], tun.Name()) 109 | ir.ifru_mtu = int(tun.mtu) 110 | 111 | // Set the MTU 112 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { 113 | err = errno 114 | tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) 115 | 116 | // Fall back to ifconfig to set the MTU 117 | cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu)) 118 | tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) 119 | output, err := cmd.CombinedOutput() 120 | if err != nil { 121 | tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) 122 | tun.log.Traceln(string(output)) 123 | } 124 | } 125 | 126 | // Create the address request 127 | // FIXME: I don't work! 128 | var ar in6_ifreq_addr 129 | copy(ar.ifr_name[:], tun.Name()) 130 | ar.ifru_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifru_addr)) 131 | ar.ifru_addr.sin6_family = unix.AF_INET6 132 | parts := strings.Split(strings.Split(addr, "/")[0], ":") 133 | for i := 0; i < 8; i++ { 134 | addr, _ := strconv.ParseUint(parts[i], 16, 16) 135 | b := make([]byte, 16) 136 | binary.LittleEndian.PutUint16(b, uint16(addr)) 137 | ar.ifru_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b)) 138 | } 139 | 140 | // Set the interface address 141 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { 142 | err = errno 143 | tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) 144 | 145 | // Fall back to ifconfig to set the address 146 | cmd := exec.Command("ifconfig", tun.Name(), "inet6", addr) 147 | tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) 148 | output, err := cmd.CombinedOutput() 149 | if err != nil { 150 | tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) 151 | tun.log.Traceln(string(output)) 152 | } 153 | } 154 | 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /src/tun/tun_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build !mobile 2 | // +build !mobile 3 | 4 | package tun 5 | 6 | // The darwin platform specific tun parts 7 | 8 | import ( 9 | "encoding/binary" 10 | "strconv" 11 | "strings" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/unix" 15 | 16 | wgtun "golang.zx2c4.com/wireguard/tun" 17 | ) 18 | 19 | // Configures the "utun" adapter with the correct IPv6 address and MTU. 20 | func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { 21 | if ifname == "auto" { 22 | ifname = "utun" 23 | } 24 | iface, err := wgtun.CreateTUN(ifname, int(mtu)) 25 | if err != nil { 26 | panic(err) 27 | } 28 | tun.iface = iface 29 | if m, err := iface.MTU(); err == nil { 30 | tun.mtu = getSupportedMTU(uint64(m)) 31 | } else { 32 | tun.mtu = 0 33 | } 34 | return tun.setupAddress(addr) 35 | } 36 | 37 | const ( 38 | darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h 39 | darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h 40 | darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h 41 | darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h 42 | ) 43 | 44 | // nolint:structcheck 45 | type in6_addrlifetime struct { 46 | ia6t_expire float64 // nolint:unused 47 | ia6t_preferred float64 // nolint:unused 48 | ia6t_vltime uint32 49 | ia6t_pltime uint32 50 | } 51 | 52 | // nolint:structcheck 53 | type sockaddr_in6 struct { 54 | sin6_len uint8 55 | sin6_family uint8 56 | sin6_port uint8 // nolint:unused 57 | sin6_flowinfo uint32 // nolint:unused 58 | sin6_addr [8]uint16 59 | sin6_scope_id uint32 // nolint:unused 60 | } 61 | 62 | // nolint:structcheck 63 | type in6_aliasreq struct { 64 | ifra_name [16]byte 65 | ifra_addr sockaddr_in6 66 | ifra_dstaddr sockaddr_in6 // nolint:unused 67 | ifra_prefixmask sockaddr_in6 68 | ifra_flags uint32 69 | ifra_lifetime in6_addrlifetime 70 | } 71 | 72 | type ifreq struct { 73 | ifr_name [16]byte 74 | ifru_mtu uint32 75 | } 76 | 77 | // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using 78 | // a system socket and making direct syscalls to the kernel. 79 | func (tun *TunAdapter) setupAddress(addr string) error { 80 | var fd int 81 | var err error 82 | 83 | if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { 84 | tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) 85 | return err 86 | } 87 | 88 | var ar in6_aliasreq 89 | copy(ar.ifra_name[:], tun.Name()) 90 | 91 | ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask)) 92 | b := make([]byte, 16) 93 | binary.LittleEndian.PutUint16(b, uint16(0xFE00)) 94 | ar.ifra_prefixmask.sin6_addr[0] = binary.BigEndian.Uint16(b) 95 | 96 | ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr)) 97 | ar.ifra_addr.sin6_family = unix.AF_INET6 98 | parts := strings.Split(strings.Split(addr, "/")[0], ":") 99 | for i := 0; i < 8; i++ { 100 | addr, _ := strconv.ParseUint(parts[i], 16, 16) 101 | b := make([]byte, 16) 102 | binary.LittleEndian.PutUint16(b, uint16(addr)) 103 | ar.ifra_addr.sin6_addr[i] = binary.BigEndian.Uint16(b) 104 | } 105 | 106 | ar.ifra_flags |= darwin_IN6_IFF_NODAD 107 | ar.ifra_flags |= darwin_IN6_IFF_SECURED 108 | 109 | ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME 110 | ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME 111 | 112 | var ir ifreq 113 | copy(ir.ifr_name[:], tun.Name()) 114 | ir.ifru_mtu = uint32(tun.mtu) 115 | 116 | tun.log.Infof("Interface name: %s", ar.ifra_name) 117 | tun.log.Infof("Interface IPv6: %s", addr) 118 | tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) 119 | 120 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { 121 | err = errno 122 | tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) 123 | return err 124 | } 125 | 126 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { 127 | err = errno 128 | tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) 129 | return err 130 | } 131 | 132 | return err 133 | } 134 | -------------------------------------------------------------------------------- /src/tun/tun_linux.go: -------------------------------------------------------------------------------- 1 | //go:build !mobile 2 | // +build !mobile 3 | 4 | package tun 5 | 6 | // The linux platform specific tun parts 7 | 8 | import ( 9 | "github.com/vishvananda/netlink" 10 | wgtun "golang.zx2c4.com/wireguard/tun" 11 | ) 12 | 13 | // Configures the TUN adapter with the correct IPv6 address and MTU. 14 | func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { 15 | if ifname == "auto" { 16 | ifname = "\000" 17 | } 18 | iface, err := wgtun.CreateTUN(ifname, int(mtu)) 19 | if err != nil { 20 | panic(err) 21 | } 22 | tun.iface = iface 23 | if mtu, err := iface.MTU(); err == nil { 24 | tun.mtu = getSupportedMTU(uint64(mtu)) 25 | } else { 26 | tun.mtu = 0 27 | } 28 | return tun.setupAddress(addr) 29 | } 30 | 31 | // Configures the TUN adapter with the correct IPv6 address and MTU. Netlink 32 | // is used to do this, so there is not a hard requirement on "ip" or "ifconfig" 33 | // to exist on the system, but this will fail if Netlink is not present in the 34 | // kernel (it nearly always is). 35 | func (tun *TunAdapter) setupAddress(addr string) error { 36 | nladdr, err := netlink.ParseAddr(addr) 37 | if err != nil { 38 | return err 39 | } 40 | nlintf, err := netlink.LinkByName(tun.Name()) 41 | if err != nil { 42 | return err 43 | } 44 | if err := netlink.AddrAdd(nlintf, nladdr); err != nil { 45 | return err 46 | } 47 | if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil { 48 | return err 49 | } 50 | if err := netlink.LinkSetUp(nlintf); err != nil { 51 | return err 52 | } 53 | // Friendly output 54 | tun.log.Infof("Interface name: %s", tun.Name()) 55 | tun.log.Infof("Interface IPv6: %s", addr) 56 | tun.log.Infof("Interface MTU: %d", tun.mtu) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /src/tun/tun_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile 2 | // +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile 3 | 4 | package tun 5 | 6 | // This is to catch unsupported platforms 7 | // If your platform supports tun devices, you could try configuring it manually 8 | 9 | import ( 10 | wgtun "golang.zx2c4.com/wireguard/tun" 11 | ) 12 | 13 | // Configures the TUN adapter with the correct IPv6 address and MTU. 14 | func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { 15 | iface, err := wgtun.CreateTUN(ifname, mtu) 16 | if err != nil { 17 | panic(err) 18 | } 19 | tun.iface = iface 20 | if mtu, err := iface.MTU(); err == nil { 21 | tun.mtu = getSupportedMTU(uint64(mtu)) 22 | } else { 23 | tun.mtu = 0 24 | } 25 | return tun.setupAddress(addr) 26 | } 27 | 28 | // We don't know how to set the IPv6 address on an unknown platform, therefore 29 | // write about it to stdout and don't try to do anything further. 30 | func (tun *TunAdapter) setupAddress(addr string) error { 31 | tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.Name(), "to", addr) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /src/tun/tun_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package tun 5 | 6 | import ( 7 | "crypto/sha1" 8 | "encoding/binary" 9 | "errors" 10 | 11 | "log" 12 | "net/netip" 13 | "time" 14 | _ "unsafe" 15 | 16 | "golang.org/x/sys/windows" 17 | 18 | "golang.zx2c4.com/wintun" 19 | wgtun "golang.zx2c4.com/wireguard/tun" 20 | "golang.zx2c4.com/wireguard/windows/elevate" 21 | "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" 22 | 23 | "github.com/RiV-chain/RiV-mesh/src/defaults" 24 | ) 25 | 26 | // Configures the TUN adapter with the correct IPv6 address and MTU. 27 | func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { 28 | if ifname == "auto" { 29 | ifname = defaults.GetDefaults().DefaultIfName 30 | } 31 | return elevate.DoAsSystem(func() error { 32 | var err error 33 | var iface wgtun.Device 34 | var guid windows.GUID 35 | hash := sha1.Sum([]byte(ifname)) 36 | guid.Data1 = binary.LittleEndian.Uint32(hash[0:4]) 37 | guid.Data2 = binary.LittleEndian.Uint16(hash[4:6]) 38 | guid.Data3 = binary.LittleEndian.Uint16(hash[6:8]) 39 | copy(guid.Data4[:], hash[8:16]) 40 | 41 | iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)) 42 | if err != nil { 43 | wintun.Uninstall() 44 | iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)) 45 | if err != nil { 46 | return err 47 | } 48 | } 49 | 50 | tun.iface = iface 51 | for i := 1; i < 10; i++ { 52 | if err = tun.setupAddress(addr); err != nil { 53 | tun.log.Errorln("Failed to set up TUN address:", err) 54 | log.Printf("waiting...") 55 | if i > 8 { 56 | return err 57 | } else { 58 | time.Sleep(time.Duration(2*i) * time.Second) 59 | } 60 | } else { 61 | break 62 | } 63 | } 64 | if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil { 65 | tun.log.Errorln("Failed to set up TUN MTU:", err) 66 | return err 67 | } 68 | if mtu, err := iface.MTU(); err == nil { 69 | tun.mtu = uint64(mtu) 70 | } 71 | return nil 72 | }) 73 | } 74 | 75 | // Sets the MTU of the TUN adapter. 76 | func (tun *TunAdapter) setupMTU(mtu uint64) error { 77 | if tun.iface == nil || tun.Name() == "" { 78 | return errors.New("Can't configure MTU as TUN adapter is not present") 79 | } 80 | if intf, ok := tun.iface.(*wgtun.NativeTun); ok { 81 | luid := winipcfg.LUID(intf.LUID()) 82 | ipfamily, err := luid.IPInterface(windows.AF_INET6) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | ipfamily.NLMTU = uint32(mtu) 88 | intf.ForceMTU(int(ipfamily.NLMTU)) 89 | ipfamily.UseAutomaticMetric = false 90 | ipfamily.Metric = 0 91 | ipfamily.DadTransmits = 0 92 | ipfamily.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled 93 | 94 | if err := ipfamily.Set(); err != nil { 95 | return err 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | 102 | // Sets the IPv6 address of the TUN adapter. 103 | func (tun *TunAdapter) setupAddress(addr string) error { 104 | if tun.iface == nil || tun.Name() == "" { 105 | return errors.New("Can't configure IPv6 address as TUN adapter is not present") 106 | } 107 | if intf, ok := tun.iface.(*wgtun.NativeTun); ok { 108 | if address, err := netip.ParsePrefix(addr); err == nil { 109 | luid := winipcfg.LUID(intf.LUID()) 110 | addresses := []netip.Prefix{address} 111 | 112 | err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) 113 | if err == windows.ERROR_OBJECT_ALREADY_EXISTS { 114 | cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses) 115 | err = luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) 116 | } 117 | if err != nil { 118 | return err 119 | } 120 | } else { 121 | return err 122 | } 123 | } else { 124 | return errors.New("unable to get native TUN") 125 | } 126 | return nil 127 | } 128 | 129 | /* 130 | * cleanupAddressesOnDisconnectedInterfaces 131 | * SPDX-License-Identifier: MIT 132 | * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. 133 | */ 134 | func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) { 135 | if len(addresses) == 0 { 136 | return 137 | } 138 | addrHash := make(map[netip.Addr]bool, len(addresses)) 139 | for i := range addresses { 140 | addrHash[addresses[i].Addr()] = true 141 | } 142 | interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) 143 | if err != nil { 144 | return 145 | } 146 | for _, iface := range interfaces { 147 | if iface.OperStatus == winipcfg.IfOperStatusUp { 148 | continue 149 | } 150 | for address := iface.FirstUnicastAddress; address != nil; address = address.Next { 151 | if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] { 152 | prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength)) 153 | log.Printf("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName()) 154 | iface.LUID.DeleteIPAddress(prefix) 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var buildName string 4 | var buildVersion string 5 | 6 | // BuildName gets the current build name. This is usually injected if built 7 | // from git, or returns "unknown" otherwise. 8 | func BuildName() string { 9 | if buildName == "" { 10 | return "unknown" 11 | } 12 | return buildName 13 | } 14 | 15 | // BuildVersion gets the current build version. This is usually injected if 16 | // built from git, or returns "unknown" otherwise. 17 | func BuildVersion() string { 18 | if buildVersion == "" { 19 | return "unknown" 20 | } 21 | return buildVersion 22 | } 23 | --------------------------------------------------------------------------------