packages/hyprspace: import source code from 'https://github.com/hyprspace/hyprspace'

This commit is contained in:
Max Headroom 2022-09-25 21:04:44 +02:00
parent 2aa1a7c080
commit 9df627a8d2
27 changed files with 3304 additions and 204 deletions

View file

@ -0,0 +1 @@
/hyprspace.png

View file

@ -1,72 +0,0 @@
From 06370f8afc1a00da62757137f2f739c531ccfdfc Mon Sep 17 00:00:00 2001
From: Tiago Carvalho <sugoiuguu@tfwno.gf>
Date: Sat, 5 Feb 2022 15:15:24 +0000
Subject: [PATCH 1/3] Lain ipfs bootstrap nodes
---
p2p/node.go | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/p2p/node.go b/p2p/node.go
index 65d13c8..736101a 100644
--- a/p2p/node.go
+++ b/p2p/node.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
+ "os"
"sync"
"github.com/ipfs/go-datastore"
@@ -12,6 +13,7 @@ import (
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
+ "github.com/libp2p/go-libp2p-core/pnet"
dht "github.com/libp2p/go-libp2p-kad-dht"
libp2pquic "github.com/libp2p/go-libp2p-quic-transport"
"github.com/libp2p/go-tcp-transport"
@@ -29,14 +31,22 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
return
}
+ swarmKey, err := os.Open(os.Getenv("HYPRSPACE_SWARM_KEY"))
+ if err != nil {
+ return
+ }
+
ip6quic := fmt.Sprintf("/ip6/::/udp/%d/quic", port)
ip4quic := fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port)
ip6tcp := fmt.Sprintf("/ip6/::/tcp/%d", port)
ip4tcp := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port)
+ key, _ := pnet.DecodeV1PSK(swarmKey)
+
// Create libp2p node
node, err = libp2p.New(
+ libp2p.PrivateNetwork(key),
libp2p.ListenAddrStrings(ip6quic, ip4quic, ip6tcp, ip4tcp),
libp2p.Identity(privateKey),
libp2p.DefaultSecurity,
@@ -58,12 +68,10 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
// Define Bootstrap Nodes.
peers := []string{
- "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
- "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
- "/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
- "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
- "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
- "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
+ "/ip4/168.235.67.108/tcp/4001/p2p/QmRMA5pWXtfuW1y5w2t9gYxrDDD6bPRLKdWAYnHTeCxZMm",
+ "/ip4/95.216.8.12/tcp/110/p2p/Qmd7QHZU8UjfYdwmjmq1SBh9pvER9AwHpfwQvnvNo3HBBo",
+ "/ip6/2001:41d0:800:1402::3f16:3fb5/tcp/4001/p2p/12D3KooWDUgNsoLVauCDpRAo54mc4whoBudgeXQnZZK2iVYhBLCN",
+ "/ip6/2001:818:da65:e400:a553:fbc1:f0b1:5743/tcp/4001/p2p/12D3KooWC1RZxLvAeEFNTZWk1FWc1sZZ3yemF4FNNRYa3X854KJ8",
}
// Convert Bootstap Nodes into usable addresses.
--
2.34.1

View file

@ -1,48 +0,0 @@
From 743c2cc62daa48bbdcce038ad21805b906e8ddaf Mon Sep 17 00:00:00 2001
From: Tiago Carvalho <sugoiuguu@tfwno.gf>
Date: Sat, 5 Feb 2022 15:16:33 +0000
Subject: [PATCH 2/3] Remove quic transport for Lain ipfs
---
p2p/node.go | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/p2p/node.go b/p2p/node.go
index 736101a..2f86317 100644
--- a/p2p/node.go
+++ b/p2p/node.go
@@ -15,7 +15,6 @@ import (
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/pnet"
dht "github.com/libp2p/go-libp2p-kad-dht"
- libp2pquic "github.com/libp2p/go-libp2p-quic-transport"
"github.com/libp2p/go-tcp-transport"
ma "github.com/multiformats/go-multiaddr"
)
@@ -36,9 +35,6 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
return
}
- ip6quic := fmt.Sprintf("/ip6/::/udp/%d/quic", port)
- ip4quic := fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port)
-
ip6tcp := fmt.Sprintf("/ip6/::/tcp/%d", port)
ip4tcp := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port)
@@ -47,12 +43,11 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
// Create libp2p node
node, err = libp2p.New(
libp2p.PrivateNetwork(key),
- libp2p.ListenAddrStrings(ip6quic, ip4quic, ip6tcp, ip4tcp),
+ libp2p.ListenAddrStrings(ip6tcp, ip4tcp),
libp2p.Identity(privateKey),
libp2p.DefaultSecurity,
libp2p.NATPortMap(),
libp2p.DefaultMuxers,
- libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(tcp.NewTCPTransport),
libp2p.FallbackDefaults,
)
--
2.34.1

View file

@ -1,24 +0,0 @@
From 377b0a8f56b04e693a4d4c7a0b9bb674c63c5bba Mon Sep 17 00:00:00 2001
From: Tiago Carvalho <sugoiuguu@tfwno.gf>
Date: Sat, 5 Feb 2022 15:16:45 +0000
Subject: [PATCH 3/3] Remove dep from go.mod
---
go.mod | 1 -
1 file changed, 1 deletion(-)
diff --git a/go.mod b/go.mod
index 672c2fc..1092553 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,6 @@ require (
github.com/libp2p/go-libp2p v0.17.0
github.com/libp2p/go-libp2p-core v0.13.0
github.com/libp2p/go-libp2p-kad-dht v0.15.0
- github.com/libp2p/go-libp2p-quic-transport v0.15.2
github.com/libp2p/go-tcp-transport v0.4.0
github.com/multiformats/go-multiaddr v0.4.1
github.com/nxadm/tail v1.4.8
--
2.34.1

View file

@ -1,26 +0,0 @@
From 46110b055eaaa0c1f815ff876da4713499c17bc8 Mon Sep 17 00:00:00 2001
From: Max <max@privatevoid.net>
Date: Fri, 17 Jun 2022 22:17:08 +0200
Subject: [PATCH 4/4] Use more NAT traversal features
---
p2p/node.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/p2p/node.go b/p2p/node.go
index 2f86317..0c9a250 100644
--- a/p2p/node.go
+++ b/p2p/node.go
@@ -49,6 +49,9 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
libp2p.NATPortMap(),
libp2p.DefaultMuxers,
libp2p.Transport(tcp.NewTCPTransport),
+ libp2p.EnableHolePunching(),
+ libp2p.EnableRelayService(),
+ libp2p.EnableNATService(),
libp2p.FallbackDefaults,
)
if err != nil {
--
2.36.0

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,204 @@
<img src="https://cdn.privatevoid.net/assets/ba/f010319e49e26c15063df091b495396442f5f3d4463093b86ed400569f5473" width="250">
# Hyprspace
[![Go Report Card](https://goreportcard.com/badge/github.com/hyprspace/hyprspace)](https://goreportcard.com/report/github.com/hyprspace/hyprspace)
[![](https://img.shields.io/matrix/hyprspace:matrix.org)](https://matrix.to/#/%23hyprspace:matrix.org)
A Lightweight VPN Built on top of IPFS & Libp2p for Truly Distributed Networks.
https://user-images.githubusercontent.com/19558067/152407636-a5f4ae1f-9493-4346-bf73-0de109928415.mp4
## Table of Contents
- [A Bit of Backstory](#a-bit-of-backstory)
- [Use Cases](#use-cases)
- [A Digital Nomad](#a-digital-nomad)
- [A Privacy Advocate](#a-privacy-advocate)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Usage](#usage)
- [Commands](#commands)
- [Tutorial](#tutorial)
## A Bit of Backstory
[Libp2p](https://libp2p.io) is a networking library created by [Protocol Labs](https://protocol.ai) that allows nodes to discover each other using a Distributed Hash Table. Paired with [NAT hole punching](https://en.wikipedia.org/wiki/Hole_punching_(networking)) this allows Hyprspace to create a direct encrypted tunnel between two nodes even if they're both behind firewalls.
**Moreover! Each node doesn't even need to know the other's ip address prior to starting up the connection.** This makes Hyprspace perfect for devices that frequently migrate between locations but still require a constant virtual ip address.
### So How Does Hyprspace Compare to Something Like Wireguard?
[WireGuard](https://wireguard.com) is an amazing VPN written by Jason A. Donenfeld. If you haven't already, definitely go check it out! WireGuard actually inspired me to write Hyprspace. That said, although WireGuard is in a class of its own as a great VPN, it requires at least one of your nodes to have a public IP address. In this mode, as long as one of your nodes is publicly accessible, it can be used as a central relay to reach the other nodes in the network. However, this means that all of the traffic for your entire system is going through that one system which can slow down your network and make it fragile in the case that node goes down and you lose the whole network. So instead say that you want each node to be able to directly connect to each other as they do in Hyprspace. Unfortunately through WireGuard this would require every node to be publicly addressable which means manual port forwarding and no travelling nodes.
By contrast Hyprspace allows all of your nodes to connect directly to each other creating a strong reliable network even if they're all behind their own NATs/firewalls. No manual port forwarding required!
## Use Cases:
##### A Digital Nomad
I use this system when travelling, if I'm staying in a rental or hotel and want to try something out on a Raspberry Pi I can plug the Pi into the location's router or ethernet port and then just ssh into the system using the same-old internal Hyprspace ip address without having to worry about their NAT or local firewall. Furthermore, if I'm connected to the Virtual Hyprspace Network I can ssh into my machines at home without requiring me to set up any sort of port forwarding.
##### A Privacy Advocate
Honestly, I even use this system when I'm at home and could connect directly to my local infrastructure. Using Hyprspace however, I don't have to trust the security of my local network and Hyprspace will intelligently connect to my machines using their local ip addresses for maximum speed.
If anyone else has some use cases please add them! Pull requests welcome!
| :exclamation: | Hyprspace is still a very new project. Although we've tested the code locally for security, it hasn't been audited by a third party yet. We probably wouldn't trust it yet in high security environments. |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## Getting Started
### Prerequisites
If you're running Hyprspace on Windows you'll need to install [tap-windows](http://build.openvpn.net/downloads/releases/).
### Installation
#### Automatic (Linux & MacOS)
```
curl -L https://hyprspace.io/install.sh | bash
```
#### Manual
1. Go to Hyprspace Releases (over there -->)
2. Copy the link for your corresponding OS and Architecture.
3. Run `sudo mkdir -p /usr/local/bin/`
4. Run `sudo curl -L "PATH-TO-RELEASE" -o /usr/local/bin/hyprspace`
5. Run `sudo chmod a+x /usr/local/bin/hyprspace`
6. (Optional) Run `sudo ln -s /usr/local/bin/hyprspace /usr/bin/hyprspace`
## Usage
### Commands
| Command | Alias | Description |
| ------------------- | ------- | -------------------------------------------------------------------------- |
| `help` | `?` | Get help with a specific subcommand. |
| `init` | `i` | Initialize an interface's configuration. |
| `up` | `up` | Create and Bring Up a Hyprspace Interface |
| `down ` | `d` | Bring Down and Delete A Hyprspace Interface |
| `update` | `upd` | Have Hyprspace update its own binary to the latest release. |
### Global Flags
| Flag | Alias | Description |
| ------------------- | ------- | -------------------------------------------------------------------------- |
| `--config` | `-c` | Specify the path to a hyprspace config for an interface. |
## Tutorial
### Initializing an Interface
The first thing we'll want to do once we've got Hyprspace installed is
initialize the configuration for an interface. In this case we'll call the
interface on our local machine `hs0` (for hypr-space 0) and `hs1` on our remote server
but yours could be anything you'd like.
(Note: if you're using a Mac you'll have to use the interface name `utun[0-9]`. Check which interfaces are already in use by running `ip a` once you've got `iproute2mac` installed.)
(Note: if you're using Windows you'll have to use the interface name as seen in Control Panel. IP address will be set automatically only if you run Hyprspace as Administrator.)
###### Local Machine
```bash
sudo hyprspace init hs0
```
###### Remote Machine
```bash
sudo hyprspace init hs1
```
### Add Each Machine As A Peer Of The Other
Now that we've got a set of configurations we'll want to
tell the machines about each other. By default Hyprspace will
put the interface configurations in `/etc/hyprspace/interface-name.yaml`.
So for our example we'll run
###### Local Machine
```bash
sudo nano /etc/hyprspace/hs0.yaml
```
and
###### Remote Machine
```bash
sudo nano /etc/hyprspace/hs1.yaml
```
### Update Peer Configs
Now in each config we'll add the other machine's ID as a peer.
You can find each machine's ID at the top of their configuration file.
Update,
```yaml
peers: {}
```
to
```yaml
peers:
10.1.1.2:
id: YOUR-OTHER-PEER-ID
```
Notice here we'll have to pick one of our machines to be `10.1.1.1`
and the other to be `10.1.1.2`. Make sure to update the interface's IP
address for the machine who needs to change to be `10.1.1.2`.
### Starting Up the Interfaces!
Now that we've got our configs all sorted we can start up the two interfaces!
###### Local Machine
```bash
sudo hyprspace up hs0
```
and
###### Remote Machine
```bash
sudo hyprspace up hs1
```
After a few seconds you should see a the network finish setting up
and find your other machine. We can now test the connection by
pinging back and forth across the network.
###### Local Machine
```bash
ping 10.1.1.2
```
### Stopping the Interface and Cleaning Up
Now to stop the interface and clean up the system you can run,
###### Local Machine
```bash
sudo hyprspace down hs0
```
and,
###### Remote Machine
```bash
sudo hyprspace down hs1
```
## Disclaimer & Copyright
WireGuard is a registered trademark of Jason A. Donenfeld.
## License
Copyright 2021-2022 Alec Scott <hi@alecbcs.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,62 @@
package cli
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/tun"
)
// Down brings down a Hyprspace interface and removes it from the system.
var Down = cmd.Sub{
Name: "down",
Alias: "d",
Short: "Bring Down A Hyprspace Interface.",
Args: &DownArgs{},
Run: DownRun,
}
// DownArgs handles the specific arguments for the down command.
type DownArgs struct {
InterfaceName string
}
// DownRun handles the execution of the down command.
func DownRun(r *cmd.Root, c *cmd.Sub) {
// Parse Command Args
args := c.Args.(*DownArgs)
// Parse Global Config Flag for Custom Config Path
configPath := r.Flags.(*GlobalFlags).Config
if configPath == "" {
configPath = "/etc/hyprspace/" + args.InterfaceName + ".yaml"
}
// Read lock from file system to stop process.
lockPath := filepath.Join(filepath.Dir(configPath), args.InterfaceName+".lock")
out, err := os.ReadFile(lockPath)
checkErr(err)
pid, err := strconv.Atoi(string(out))
checkErr(err)
process, err := os.FindProcess(pid)
checkErr(err)
err0 := process.Signal(os.Interrupt)
err1 := tun.Delete(args.InterfaceName)
// Different types of systems may need the tun devices destroyed first or
// the process to exit first don't worry as long as one of these two has
// succeeded.
if err0 != nil && err1 != nil {
checkErr(err0)
checkErr(err1)
}
fmt.Println("[+] deleted hyprspace " + args.InterfaceName + " daemon")
}

View file

@ -0,0 +1,86 @@
package cli
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/config"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"gopkg.in/yaml.v2"
)
// Init creates a configuration for a Hyprspace Interface.
var Init = cmd.Sub{
Name: "init",
Alias: "i",
Short: "Initialize An Interface Config",
Args: &InitArgs{},
Run: InitRun,
}
// InitArgs handles the specific arguments for the init command.
type InitArgs struct {
InterfaceName string
}
// InitRun handles the execution of the init command.
func InitRun(r *cmd.Root, c *cmd.Sub) {
// Parse Command Arguments
args := c.Args.(*InitArgs)
// Parse Global Config Flag
configPath := r.Flags.(*GlobalFlags).Config
if configPath == "" {
configPath = "/etc/hyprspace/" + args.InterfaceName + ".yaml"
}
// Create New Libp2p Node
host, err := libp2p.New()
checkErr(err)
// Get Node's Private Key
keyBytes, err := crypto.MarshalPrivateKey(host.Peerstore().PrivKey(host.ID()))
checkErr(err)
// Setup an initial default command.
new := config.Config{
Interface: config.Interface{
Name: args.InterfaceName,
ListenPort: 8001,
Address: "10.1.1.1/24",
ID: host.ID().Pretty(),
PrivateKey: string(keyBytes),
},
}
out, err := yaml.Marshal(&new)
checkErr(err)
err = os.MkdirAll(filepath.Dir(configPath), os.ModePerm)
checkErr(err)
f, err := os.Create(configPath)
checkErr(err)
// Write out config to file.
_, err = f.Write(out)
checkErr(err)
err = f.Close()
checkErr(err)
// Print config creation message to user
fmt.Printf("Initialized new config at %s\n", configPath)
fmt.Println("To edit the config run,")
fmt.Println()
if strings.HasPrefix(configPath, "/etc/") {
fmt.Printf(" sudo nano %s\n", configPath)
} else {
fmt.Printf(" nano %s\n", configPath)
}
fmt.Println()
}

View file

@ -0,0 +1,63 @@
package cli
import (
"fmt"
"log"
"sync"
"time"
"github.com/DataDrake/cli-ng/v2/cmd"
)
var appVersion string = "develop"
//GlobalFlags contains the flags for commands.
type GlobalFlags struct {
Config string `short:"c" long:"config" desc:"Specify a custom config path."`
}
// Root is the main command.
var Root *cmd.Root
func init() {
Root = &cmd.Root{
Name: "hyprspace",
Short: "Hyprspace Distributed Network",
Version: appVersion,
Flags: &GlobalFlags{},
}
cmd.Register(&cmd.Help)
cmd.Register(&Init)
cmd.Register(&Up)
cmd.Register(&Down)
cmd.Register(&Update)
cmd.Register(&cmd.Version)
}
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
// Spinner is an array of the progression of the spinner.
var Spinner = []string{"|", "/", "-", "\\"}
// SpinnerWait displays the actual spinner
func SpinnerWait(done chan int, message string, wg *sync.WaitGroup) {
ticker := time.NewTicker(time.Millisecond * 128)
frameCounter := 0
for {
select {
case <-done:
wg.Done()
return
default:
<-ticker.C
ind := frameCounter % len(Spinner)
fmt.Printf("\r[%v] "+message, Spinner[ind])
frameCounter++
}
}
}

View file

@ -0,0 +1,416 @@
package cli
import (
"context"
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/config"
"github.com/hyprspace/hyprspace/p2p"
"github.com/hyprspace/hyprspace/tun"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/nxadm/tail"
)
var (
// iface is the tun device used to pass packets between
// Hyprspace and the user's machine.
tunDev *tun.TUN
// RevLookup allow quick lookups of an incoming stream
// for security before accepting or responding to any data.
RevLookup map[string]string
// activeStreams is a map of active streams to a peer
activeStreams map[string]network.Stream
)
// Up creates and brings up a Hyprspace Interface.
var Up = cmd.Sub{
Name: "up",
Alias: "up",
Short: "Create and Bring Up a Hyprspace Interface.",
Args: &UpArgs{},
Flags: &UpFlags{},
Run: UpRun,
}
// UpArgs handles the specific arguments for the up command.
type UpArgs struct {
InterfaceName string
}
// UpFlags handles the specific flags for the up command.
type UpFlags struct {
Foreground bool `short:"f" long:"foreground" desc:"Don't Create Background Daemon."`
}
// UpRun handles the execution of the up command.
func UpRun(r *cmd.Root, c *cmd.Sub) {
// Parse Command Args
args := c.Args.(*UpArgs)
// Parse Command Flags
flags := c.Flags.(*UpFlags)
// Parse Global Config Flag for Custom Config Path
configPath := r.Flags.(*GlobalFlags).Config
if configPath == "" {
configPath = "/etc/hyprspace/" + args.InterfaceName + ".yaml"
}
// Read in configuration from file.
cfg, err := config.Read(configPath)
checkErr(err)
if !flags.Foreground {
if err := createDaemon(cfg); err != nil {
fmt.Println("[+] Failed to Create Hyprspace Daemon")
fmt.Println(err)
} else {
fmt.Println("[+] Successfully Created Hyprspace Daemon")
}
return
}
// Setup reverse lookup hash map for authentication.
RevLookup = make(map[string]string, len(cfg.Peers))
for ip, id := range cfg.Peers {
RevLookup[id.ID] = ip
}
fmt.Println("[+] Creating TUN Device")
if runtime.GOOS == "darwin" {
if len(cfg.Peers) > 1 {
checkErr(errors.New("cannot create interface macos does not support more than one peer"))
}
// Grab ip address of only peer in config
var destPeer string
for ip := range cfg.Peers {
destPeer = ip
}
// Create new TUN device
tunDev, err = tun.New(
cfg.Interface.Name,
tun.Address(cfg.Interface.Address),
tun.DestAddress(destPeer),
tun.MTU(1420),
)
} else {
// Create new TUN device
tunDev, err = tun.New(
cfg.Interface.Name,
tun.Address(cfg.Interface.Address),
tun.MTU(1420),
)
}
if err != nil {
checkErr(err)
}
// Setup System Context
ctx := context.Background()
fmt.Println("[+] Creating LibP2P Node")
// Check that the listener port is available.
port, err := verifyPort(cfg.Interface.ListenPort)
checkErr(err)
// Create P2P Node
host, dht, err := p2p.CreateNode(
ctx,
cfg.Interface.PrivateKey,
port,
streamHandler,
)
checkErr(err)
// Setup Peer Table for Quick Packet --> Dest ID lookup
peerTable := make(map[string]peer.ID)
for ip, id := range cfg.Peers {
peerTable[ip], err = peer.Decode(id.ID)
checkErr(err)
}
fmt.Println("[+] Setting Up Node Discovery via DHT")
// Setup P2P Discovery
go p2p.Discover(ctx, host, dht, peerTable)
go prettyDiscovery(ctx, host, peerTable)
// Configure path for lock
lockPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".lock")
// Register the application to listen for SIGINT/SIGTERM
go signalExit(host, lockPath)
// Write lock to filesystem to indicate an existing running daemon.
err = os.WriteFile(lockPath, []byte(fmt.Sprint(os.Getpid())), os.ModePerm)
checkErr(err)
// Bring Up TUN Device
err = tunDev.Up()
if err != nil {
checkErr(errors.New("unable to bring up tun device"))
}
fmt.Println("[+] Network Setup Complete...Waiting on Node Discovery")
// + ----------------------------------------+
// | Listen For New Packets on TUN Interface |
// + ----------------------------------------+
// Initialize active streams map and packet byte array.
activeStreams = make(map[string]network.Stream)
var packet = make([]byte, 1420)
for {
// Read in a packet from the tun device.
plen, err := tunDev.Iface.Read(packet)
if err != nil {
log.Println(err)
continue
}
// Decode the packet's destination address
dst := net.IPv4(packet[16], packet[17], packet[18], packet[19]).String()
// Check if we already have an open connection to the destination peer.
stream, ok := activeStreams[dst]
if ok {
// Write out the packet's length to the libp2p stream to ensure
// we know the full size of the packet at the other end.
err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err == nil {
// Write the packet out to the libp2p stream.
// If everyting succeeds continue on to the next packet.
_, err = stream.Write(packet[:plen])
if err == nil {
continue
}
}
// If we encounter an error when writing to a stream we should
// close that stream and delete it from the active stream map.
stream.Close()
delete(activeStreams, dst)
}
// Check if the destination of the packet is a known peer to
// the interface.
if peer, ok := peerTable[dst]; ok {
stream, err = host.NewStream(ctx, peer, p2p.Protocol)
if err != nil {
continue
}
// Write packet length
err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err != nil {
stream.Close()
continue
}
// Write the packet
_, err = stream.Write(packet[:plen])
if err != nil {
stream.Close()
continue
}
// If all succeeds when writing the packet to the stream
// we should reuse this stream by adding it active streams map.
activeStreams[dst] = stream
}
}
}
// singalExit registers two syscall handlers on the system so that if
// an SIGINT or SIGTERM occur on the system hyprspace can gracefully
// shutdown and remove the filesystem lock file.
func signalExit(host host.Host, lockPath string) {
// Wait for a SIGINT or SIGTERM signal
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
// Shut the node down
err := host.Close()
checkErr(err)
// Remove daemon lock from file system.
err = os.Remove(lockPath)
checkErr(err)
fmt.Println("Received signal, shutting down...")
// Exit the application.
os.Exit(0)
}
// createDaemon handles creating an independent background process for a
// Hyprspace daemon from the original parent process.
func createDaemon(cfg *config.Config) error {
path, err := os.Executable()
checkErr(err)
// Generate log path
logPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".log")
// Create Pipe to monitor for daemon output.
f, err := os.Create(logPath)
checkErr(err)
// Create Sub Process
process, err := os.StartProcess(
path,
append(os.Args, "--foreground"),
&os.ProcAttr{
Dir: ".",
Env: os.Environ(),
Files: []*os.File{nil, f, f},
},
)
checkErr(err)
// Listen to the child process's log output to determine
// when the daemon is setup and connected to a set of peers.
count := 0
deadlineHit := false
countChan := make(chan int)
go func(out chan<- int) {
numConnected := 0
t, err := tail.TailFile(logPath, tail.Config{Follow: true})
if err != nil {
out <- numConnected
return
}
for line := range t.Lines {
fmt.Println(line.Text)
if strings.HasPrefix(line.Text, "[+] Connection to") {
numConnected++
if numConnected >= len(cfg.Peers) {
break
}
}
}
out <- numConnected
}(countChan)
// Block until all clients are connected or for a maximum of 30s.
select {
case _, deadlineHit = <-time.After(30 * time.Second):
case count = <-countChan:
}
// Release the created daemon
err = process.Release()
checkErr(err)
// Check if the daemon exited prematurely
if !deadlineHit && count < len(cfg.Peers) {
return errors.New("failed to create daemon")
}
return nil
}
func streamHandler(stream network.Stream) {
// If the remote node ID isn't in the list of known nodes don't respond.
if _, ok := RevLookup[stream.Conn().RemotePeer().Pretty()]; !ok {
stream.Reset()
return
}
var packet = make([]byte, 1420)
var packetSize = make([]byte, 2)
for {
// Read the incoming packet's size as a binary value.
_, err := stream.Read(packetSize)
if err != nil {
stream.Close()
return
}
// Decode the incoming packet's size from binary.
size := binary.LittleEndian.Uint16(packetSize)
// Read in the packet until completion.
var plen uint16 = 0
for plen < size {
tmp, err := stream.Read(packet[plen:size])
plen += uint16(tmp)
if err != nil {
stream.Close()
return
}
}
tunDev.Iface.Write(packet[:size])
}
}
func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]peer.ID) {
// Build a temporary map of peers to limit querying to only those
// not connected.
tempTable := make(map[string]peer.ID, len(peerTable))
for ip, id := range peerTable {
tempTable[ip] = id
}
for len(tempTable) > 0 {
for ip, id := range tempTable {
stream, err := node.NewStream(ctx, id, p2p.Protocol)
if err != nil && (strings.HasPrefix(err.Error(), "failed to dial") ||
strings.HasPrefix(err.Error(), "no addresses")) {
// Attempt to connect to peers slowly when they aren't found.
time.Sleep(5 * time.Second)
continue
}
if err == nil {
fmt.Printf("[+] Connection to %s Successful. Network Ready.\n", ip)
stream.Close()
}
delete(tempTable, ip)
}
}
}
func verifyPort(port int) (int, error) {
var ln net.Listener
var err error
// If a user manually sets a port don't try to automatically
// find an open port.
if port != 8001 {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
return port, errors.New("could not create node, listen port already in use by something else")
}
} else {
// Automatically look for an open port when a custom port isn't
// selected by a user.
for {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err == nil {
break
}
if port >= 65535 {
return port, errors.New("failed to find open port")
}
port++
}
}
if ln != nil {
ln.Close()
}
return port, nil
}

View file

@ -0,0 +1,83 @@
package cli
import (
"bufio"
"fmt"
"net/http"
"os"
"runtime"
"strings"
"sync"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/inconshreveable/go-update"
"github.com/tcnksm/go-latest"
)
// Update checks for a new version of the Hyprspace program and updates itself
// if a newer version is found and the user agrees to update.
var Update = cmd.Sub{
Name: "update",
Alias: "upd",
Short: "Update Hyprspace to the lastest version.",
Args: &UpdateArgs{},
Flags: &UpdateFlags{},
Run: UpdateRun,
}
// UpdateArgs handles the specific arguments for the update command.
type UpdateArgs struct {
}
// UpdateFlags handles the specific flags for the update command.
type UpdateFlags struct {
Yes bool `short:"y" long:"yes" desc:"If a newer version is found update without prompting the user."`
}
// UpdateRun handles the checking and self updating of the AIT program.
func UpdateRun(r *cmd.Root, c *cmd.Sub) {
fmt.Printf("Current Version: %s\n", appVersion)
flags := c.Flags.(*UpdateFlags)
latestVersion := &latest.GithubTag{
Owner: "hyprspace",
Repository: "hyprspace",
}
res, _ := latest.Check(latestVersion, appVersion)
fmt.Printf("Latest Version: %s\n", res.Current)
if res.Outdated {
if !flags.Yes {
fmt.Println("Would you like to update Hyprspace to the newest version? ([y]/n)")
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.ToLower(strings.TrimSpace(input))
if input == "n" {
return
}
}
url := "https://github.com/hyprspace/hyprspace/releases/download/v" + res.Current + "/hyprspace-v" + res.Current + "-" + runtime.GOOS + "-" + runtime.GOARCH
doneChan := make(chan int, 1)
wg := sync.WaitGroup{}
wg.Add(1)
// Display Spinner on Update.
go SpinnerWait(doneChan, "Updating Hyprspace...", &wg)
resp, err := http.Get(url)
checkErr(err)
defer resp.Body.Close()
err = update.Apply(resp.Body, update.Options{})
checkErr(err)
doneChan <- 0
wg.Wait()
fmt.Print("\rUpdating Hyprspace: Done!\n")
} else {
fmt.Println("Already Up-To-Date!")
}
}

View file

@ -0,0 +1,64 @@
package config
import (
"fmt"
"net"
"os"
"gopkg.in/yaml.v2"
)
// Config is the main Configuration Struct for Hyprspace.
type Config struct {
Path string `yaml:"path,omitempty"`
Interface Interface `yaml:"interface"`
Peers map[string]Peer `yaml:"peers"`
}
// Interface defines all of the fields that a local node needs to know about itself!
type Interface struct {
Name string `yaml:"name"`
ID string `yaml:"id"`
ListenPort int `yaml:"listen_port"`
Address string `yaml:"address"`
PrivateKey string `yaml:"private_key"`
}
// Peer defines a peer in the configuration. We might add more to this later.
type Peer struct {
ID string `yaml:"id"`
}
// Read initializes a config from a file.
func Read(path string) (*Config, error) {
in, err := os.ReadFile(path)
if err != nil {
return nil, err
}
result := Config{
Interface: Interface{
Name: "hs0",
ListenPort: 8001,
Address: "10.1.1.1/24",
ID: "",
PrivateKey: "",
},
}
// Read in config settings from file.
err = yaml.Unmarshal(in, &result)
if err != nil {
return nil, err
}
// Check peers have valid ip addresses
for ip := range result.Peers {
if net.ParseIP(ip).String() == "<nil>" {
return nil, fmt.Errorf("%s is not a valid ip address", ip)
}
}
// Overwrite path of config to input.
result.Path = path
return &result, nil
}

View file

@ -1,32 +0,0 @@
{ lib, stdenv, buildGoModule, fetchFromGitHub, iproute2mac }:
buildGoModule rec {
pname = "hyprspace";
version = "0.2.2";
propagatedBuildInputs = lib.optional stdenv.isDarwin iproute2mac;
patches = [
./0001-Lain-ipfs-bootstrap-nodes.patch
./0002-Remove-quic-transport-for-Lain-ipfs.patch
./0003-Remove-dep-from-go.mod.patch
./0004-Use-more-NAT-traversal-features.patch
];
src = fetchFromGitHub {
owner = pname;
repo = pname;
rev = "v${version}";
sha256 = "sha256-UlIQCy4moW58tQ1dqxrPsU5LN1Bs/Jy5X+2CEmXdYIk=";
};
vendorSha256 = "sha256-8j9M8LrcqiPShCCNOmmJoY6wclHRiX2xOJH/wvlwvwY=";
meta = with lib; {
description = "A Lightweight VPN Built on top of Libp2p for Truly Distributed Networks.";
homepage = "https://github.com/hyprspace/hyprspace";
license = licenses.asl20;
maintainers = with maintainers; [ yusdacra ];
platforms = platforms.linux ++ platforms.darwin;
};
}

View file

@ -0,0 +1,21 @@
module github.com/hyprspace/hyprspace
go 1.16
require (
github.com/DataDrake/cli-ng/v2 v2.0.2
github.com/hashicorp/go-version v1.4.0 // indirect
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/ipfs/go-datastore v0.5.1
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-libp2p v0.17.0
github.com/libp2p/go-libp2p-core v0.13.0
github.com/libp2p/go-libp2p-kad-dht v0.15.0
github.com/libp2p/go-tcp-transport v0.4.0
github.com/multiformats/go-multiaddr v0.4.1
github.com/nxadm/tail v1.4.8
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
github.com/vishvananda/netlink v1.1.0
gopkg.in/yaml.v2 v2.4.0
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
package main
import "github.com/hyprspace/hyprspace/cli"
func main() {
cli.Root.Run()
}

View file

@ -0,0 +1 @@
{"outs": [{"sha256": "baf010319e49e26c15063df091b495396442f5f3d4463093b86ed400569f5473", "size": 287300, "path": "hyprspace.png"}]}

View file

@ -0,0 +1,37 @@
package p2p
import (
"context"
"time"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
dht "github.com/libp2p/go-libp2p-kad-dht"
)
// Discover starts up a DHT based discovery system finding and adding nodes with the same rendezvous string.
func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, peerTable map[string]peer.ID) {
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
for _, id := range peerTable {
if h.Network().Connectedness(id) != network.Connected {
addrs, err := dht.FindPeer(ctx, id)
if err != nil {
continue
}
_, err = h.Network().DialPeer(ctx, addrs.ID)
if err != nil {
continue
}
}
}
}
}
}

View file

@ -0,0 +1,161 @@
package p2p
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"sync"
"github.com/ipfs/go-datastore"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/pnet"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-tcp-transport"
ma "github.com/multiformats/go-multiaddr"
)
// Protocol is a descriptor for the Hyprspace P2P Protocol.
const Protocol = "/hyprspace/0.0.1"
func getExtraBootstrapNodes(addr ma.Multiaddr) (nodesList []string) {
nodesList = []string{}
ip4, err := addr.ValueForProtocol(ma.P_IP4)
if err != nil {
return
}
port, err := addr.ValueForProtocol(ma.P_TCP)
if err != nil {
return
}
resp, err := http.PostForm("http://"+ip4+":"+port+"/api/v0/swarm/addrs", url.Values{})
defer resp.Body.Close()
apiResponse, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
var obj = map[string]map[string][]string{}
json.Unmarshal([]byte(apiResponse), &obj)
for k, v := range obj["Addrs"] {
for _, addr := range v {
nodesList = append(nodesList, (addr + "/p2p/" + k))
}
}
return
}
// CreateNode creates an internal Libp2p nodes and returns it and it's DHT Discovery service.
func CreateNode(ctx context.Context, inputKey string, port int, handler network.StreamHandler) (node host.Host, dhtOut *dht.IpfsDHT, err error) {
// Unmarshal Private Key
privateKey, err := crypto.UnmarshalPrivateKey([]byte(inputKey))
if err != nil {
return
}
swarmKey, err := os.Open(os.Getenv("HYPRSPACE_SWARM_KEY"))
if err != nil {
return
}
extraBootstrapNodes := []string{}
ipfsApiStr, ok := os.LookupEnv("HYPRSPACE_IPFS_API")
if ok {
ipfsApiAddr, err := ma.NewMultiaddr(ipfsApiStr)
if err == nil {
fmt.Println("[+] Getting additional peers from IPFS API")
extraBootstrapNodes = getExtraBootstrapNodes(ipfsApiAddr)
}
}
ip6tcp := fmt.Sprintf("/ip6/::/tcp/%d", port)
ip4tcp := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port)
key, _ := pnet.DecodeV1PSK(swarmKey)
// Create libp2p node
node, err = libp2p.New(
libp2p.PrivateNetwork(key),
libp2p.ListenAddrStrings(ip6tcp, ip4tcp),
libp2p.Identity(privateKey),
libp2p.DefaultSecurity,
libp2p.NATPortMap(),
libp2p.DefaultMuxers,
libp2p.Transport(tcp.NewTCPTransport),
libp2p.EnableHolePunching(),
libp2p.EnableRelayService(),
libp2p.EnableNATService(),
libp2p.FallbackDefaults,
)
if err != nil {
return
}
// Setup Hyprspace Stream Handler
node.SetStreamHandler(Protocol, handler)
// Create DHT Subsystem
dhtOut = dht.NewDHTClient(ctx, node, datastore.NewMapDatastore())
// Define Bootstrap Nodes.
peers := []string{
"/ip4/168.235.67.108/tcp/4001/p2p/QmRMA5pWXtfuW1y5w2t9gYxrDDD6bPRLKdWAYnHTeCxZMm",
"/ip4/95.216.8.12/tcp/4001/p2p/Qmd7QHZU8UjfYdwmjmq1SBh9pvER9AwHpfwQvnvNo3HBBo",
"/ip6/2001:41d0:800:1402::3f16:3fb5/tcp/4001/p2p/12D3KooWDUgNsoLVauCDpRAo54mc4whoBudgeXQnZZK2iVYhBLCN",
"/ip6/2001:818:da65:e400:a553:fbc1:f0b1:5743/tcp/4001/p2p/12D3KooWC1RZxLvAeEFNTZWk1FWc1sZZ3yemF4FNNRYa3X854KJ8",
}
// Convert Bootstap Nodes into usable addresses.
BootstrapPeers := make(map[peer.ID]*peer.AddrInfo, len(peers))
for _, addrStr := range append(peers, extraBootstrapNodes...) {
addr, err := ma.NewMultiaddr(addrStr)
if err != nil {
return node, dhtOut, err
}
pii, err := peer.AddrInfoFromP2pAddr(addr)
if err != nil {
return node, dhtOut, err
}
pi, ok := BootstrapPeers[pii.ID]
if !ok {
pi = &peer.AddrInfo{ID: pii.ID}
BootstrapPeers[pi.ID] = pi
}
pi.Addrs = append(pi.Addrs, pii.Addrs...)
}
// Let's connect to the bootstrap nodes first. They will tell us about the
// other nodes in the network.
var wg sync.WaitGroup
lock := sync.Mutex{}
count := 0
wg.Add(len(BootstrapPeers))
for _, peerInfo := range BootstrapPeers {
go func(peerInfo *peer.AddrInfo) {
defer wg.Done()
err := node.Connect(ctx, *peerInfo)
if err == nil {
lock.Lock()
count++
lock.Unlock()
}
}(peerInfo)
}
wg.Wait()
if count < 1 {
return node, dhtOut, errors.New("unable to bootstrap libp2p node")
}
return node, dhtOut, nil
}

View file

@ -0,0 +1,34 @@
{ inputs, lib, pkgs, ... }:
{
packages.hyprspace = with pkgs; buildGoModule {
pname = "hyprspace";
version = "0.2.2";
src = with inputs.nix-filter.lib; let
dirs = map inDirectory;
in filter {
root = ./.;
include = [
"go.mod"
"go.sum"
(matchExt "go")
] ++ (dirs [
"cli"
"config"
"p2p"
"tun"
]);
};
vendorSha256 = "sha256-8j9M8LrcqiPShCCNOmmJoY6wclHRiX2xOJH/wvlwvwY=";
meta = with lib; {
description = "A Lightweight VPN Built on top of Libp2p for Truly Distributed Networks.";
homepage = "https://github.com/hyprspace/hyprspace";
license = licenses.asl20;
maintainers = with maintainers; [ yusdacra ];
platforms = platforms.linux ++ platforms.darwin;
};
};
}

View file

@ -0,0 +1,28 @@
package tun
// Option defines a TUN device modifier option.
type Option func(tun *TUN) error
// Address sets the local address and subnet for an interface.
// On MacOS devices use this function to set the Src Address
// for an interface and use DestAddress to set the destination ip.
func Address(address string) Option {
return func(tun *TUN) error {
return tun.setAddress(address)
}
}
// MTU sets the Maximum Transmission Unit size for an interface.
func MTU(mtu int) Option {
return func(tun *TUN) error {
return tun.setMTU(mtu)
}
}
// DestAddress sets the destination address for a point-to-point interface.
// Only use this option on MacOS devices.
func DestAddress(address string) Option {
return func(tun *TUN) error {
return tun.setDestAddress(address)
}
}

View file

@ -0,0 +1,26 @@
package tun
import "github.com/songgao/water"
// TUN is a struct containing the fields necessary
// to configure a system TUN device. Access the
// internal TUN device through TUN.Iface
type TUN struct {
Iface *water.Interface
MTU int
Src string
Dst string
}
// Apply configures the specified options for a TUN device.
func (t *TUN) Apply(opts ...Option) error {
for _, opt := range opts {
if opt == nil {
continue
}
if err := opt(t); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,72 @@
//go:build darwin
// +build darwin
package tun
import (
"fmt"
"os/exec"
"github.com/songgao/water"
)
// New creates and returns a new TUN interface for the application.
func New(name string, opts ...Option) (*TUN, error) {
// Setup TUN Config
cfg := water.Config{
DeviceType: water.TUN,
}
// Create Water Interface
iface, err := water.New(cfg)
if err != nil {
return nil, err
}
// Create TUN result struct
result := TUN{
Iface: iface,
}
// Apply options to set TUN config values
err = result.Apply(opts...)
return &result, err
}
// SetMTU sets the Maximum Tansmission Unit Size for a
// Packet on the interface.
func (t *TUN) setMTU(mtu int) error {
return ifconfig(t.Iface.Name(), "mtu", fmt.Sprintf("%d", mtu))
}
// SetDestAddress sets the interface's address.
func (t *TUN) setAddress(address string) error {
t.Src = address
return nil
}
// SetDestAddress sets the interface's address.
func (t *TUN) setDestAddress(address string) error {
t.Dst = address
return nil
}
// Up brings up an interface to allow it to start accepting connections.
func (t *TUN) Up() error {
return ifconfig(t.Iface.Name(), "inet", t.Src, t.Dst, "up")
}
// Down brings down an interface stopping active connections.
func (t *TUN) Down() error {
return ifconfig(t.Iface.Name(), "down")
}
// Delete removes a TUN device from the host.
func Delete(name string) error {
return fmt.Errorf("removing an interface is unsupported under mac")
}
func ifconfig(args ...string) error {
cmd := exec.Command("ifconfig", args...)
return cmd.Run()
}

View file

@ -0,0 +1,92 @@
//go:build linux
// +build linux
package tun
import (
"errors"
"github.com/songgao/water"
"github.com/vishvananda/netlink"
)
// New creates and returns a new TUN interface for the application.
func New(name string, opts ...Option) (*TUN, error) {
// Setup TUN Config
cfg := water.Config{
DeviceType: water.TUN,
}
cfg.Name = name
// Create Water Interface
iface, err := water.New(cfg)
if err != nil {
return nil, err
}
// Create TUN result struct
result := TUN{
Iface: iface,
}
// Apply options to set TUN config values
err = result.Apply(opts...)
return &result, err
}
// setMTU sets the Maximum Tansmission Unit Size for a
// Packet on the interface.
func (t *TUN) setMTU(mtu int) error {
link, err := netlink.LinkByName(t.Iface.Name())
if err != nil {
return err
}
return netlink.LinkSetMTU(link, mtu)
}
// setDestAddress sets the interface's destination address and subnet.
func (t *TUN) setAddress(address string) error {
addr, err := netlink.ParseAddr(address)
if err != nil {
return err
}
link, err := netlink.LinkByName(t.Iface.Name())
if err != nil {
return err
}
return netlink.AddrAdd(link, addr)
}
// SetDestAddress isn't supported under Linux.
// You should instead use set address to set the interface to handle
// all addresses within a subnet.
func (t *TUN) setDestAddress(address string) error {
return errors.New("destination addresses are not supported under linux")
}
// Up brings up an interface to allow it to start accepting connections.
func (t *TUN) Up() error {
link, err := netlink.LinkByName(t.Iface.Name())
if err != nil {
return err
}
return netlink.LinkSetUp(link)
}
// Down brings down an interface stopping active connections.
func (t *TUN) Down() error {
link, err := netlink.LinkByName(t.Iface.Name())
if err != nil {
return err
}
return netlink.LinkSetDown(link)
}
// Delete removes a TUN device from the host.
func Delete(name string) error {
link, err := netlink.LinkByName(name)
if err != nil {
return err
}
return netlink.LinkDel(link)
}

View file

@ -0,0 +1,127 @@
//go:build windows
// +build windows
package tun
import (
"errors"
"fmt"
"net"
"os/exec"
"github.com/songgao/water"
)
// New creates and returns a new TUN interface for the application.
func New(name string, opts ...Option) (*TUN, error) {
result := TUN{}
// Apply options early to set struct values for interface creation.
err := result.Apply(opts...)
if err != nil {
return nil, err
}
// TUN on Windows requires address and network to be set on device creation stage
// We also set network to 0.0.0.0/0 so we able to reach networks behind the node
// https://github.com/songgao/water/blob/master/params_windows.go
// https://gitlab.com/openconnect/openconnect/-/blob/master/tun-win32.c
ip, _, err := net.ParseCIDR(result.Src)
if err != nil {
return nil, err
}
network := net.IPNet{
IP: ip,
Mask: net.IPv4Mask(0, 0, 0, 0),
}
// Setup TUN Config
cfg := water.Config{
DeviceType: water.TUN,
PlatformSpecificParams: water.PlatformSpecificParams{
ComponentID: "tap0901",
InterfaceName: name,
Network: network.String(),
},
}
// Interface should be enabled before creation of water interface
// Otherwise there will be an error "The system cannot find the file specified."
netsh("interface", "set", "interface", "name=", name, "enable")
// Create Water Interface
iface, err := water.New(cfg)
if err != nil {
return nil, err
}
// Set TUN interface to newly created interface
result.Iface = iface
// Apply options to setup TUN interface configuration
// Setup interface address
err = result.setupAddress(result.Src)
if err != nil {
return nil, err
}
// Setup interface mtu size
err = result.setupMTU(result.MTU)
if err != nil {
return nil, err
}
return &result, err
}
// setMTU configures the interface's MTU.
func (t *TUN) setMTU(mtu int) error {
t.MTU = mtu
return nil
}
// setAddress configures the interface's address.
func (t *TUN) setAddress(address string) error {
t.Src = address
return nil
}
// setupMTU sets the Maximum Tansmission Unit Size for a
// Packet on the interface.
func (t *TUN) setupMTU(mtu int) error {
return netsh("interface", "ipv4", "set", "subinterface", t.Iface.Name(), "mtu=", fmt.Sprintf("%d", mtu))
}
// setupAddress sets the interface's destination address and subnet.
func (t *TUN) setupAddress(address string) error {
return netsh("interface", "ip", "set", "address", "name=", t.Iface.Name(), "static", address)
}
// SetDestAddress isn't supported under Windows.
// You should instead use set address to set the interface to handle
// all addresses within a subnet.
func (t *TUN) setDestAddress(address string) error {
return errors.New("destination addresses are not supported under windows")
}
// Up brings up an interface to allow it to start accepting connections.
func (t *TUN) Up() error {
return nil
}
// Down brings down an interface stopping active connections.
func (t *TUN) Down() error {
return nil
}
// Delete removes a TUN device from the host.
func Delete(name string) error {
return netsh("interface", "set", "interface", "name=", name, "disable")
}
func netsh(args ...string) (err error) {
cmd := exec.Command("netsh", args...)
err = cmd.Run()
return
}

View file

@ -27,6 +27,7 @@
./modules/devshell.nix
./build-support
./networking/hyprspace/project.nix
./websites/landing/project.nix
./websites/stop-using-nix-env/project.nix
];
@ -51,8 +52,6 @@
grafana = pkgs.callPackage ./monitoring/grafana { };
hyprspace = pkgs.callPackage ./networking/hyprspace { iproute2mac = null; };
ipfs = pkgs.callPackage ./networking/ipfs { };
npins = pkgs.callPackage ./tools/npins {