├── .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 |
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 |
--------------------------------------------------------------------------------