77 Commits

Author SHA1 Message Date
dependabot[bot]
4e9f11ba17 Bump github.com/libp2p/go-libp2p from 0.17.0 to 0.18.0
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.17.0 to 0.18.0.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 21:51:17 +00:00
Alec Scott
955ceb77a6 Delete FUNDING.yml to remove GitHub Sponsors link 2022-08-20 13:55:49 -07:00
Alec Scott
9a82b53c5d Add automatic installation script link 2022-02-03 11:46:55 -07:00
Alec Scott
60a24ead86 Update README.md 2022-02-03 11:40:35 -07:00
Alec Scott
75e5857dcc Update README.md 2022-02-03 10:29:20 -07:00
Alec Scott
0f7a7c336f Add documentation to TUN library 2022-02-03 10:23:52 -07:00
Alec Scott
43d0df3731 Fix Malformed IP Parsing & Add More Documentation (#70)
* Added support for daemon logs and improved code docs

* Update the README to include wireguard disclaimer

* Update description

* Add initialization message
2022-02-03 09:08:47 -07:00
Alec Scott
d5a300a8a9 Merge branch 'main' of https://github.com/hyprspace/hyprspace 2022-02-02 10:37:29 -07:00
Alec Scott
de71ed65f2 Update logo 2022-02-02 10:36:53 -07:00
Alec
9594e99e67 Cleanup redundant section of code 2022-02-01 20:52:55 -07:00
Alec
20c306ddcb Serious Performance Improvements 2022-02-01 20:48:08 -07:00
Alec Scott
6c2a0d15c3 Update hyprspace@.service to better fix boot before network 2022-01-31 23:18:20 -07:00
Alec Scott
dcbdbe331a Merge pull request #69 from hyprspace/stream-reuse
Improve Performance by Reusing Libp2p Streams
2022-01-31 20:08:17 -07:00
Alec Scott
275eb18ca8 Add support for daemon locks for better process removal 2022-01-31 16:44:06 -07:00
Alec
39b23f4e91 Update go.mod dependencies 2022-01-30 21:50:10 -07:00
Alec
9cc7c03eb3 Update language in delete cmd 2022-01-30 21:49:34 -07:00
Alec
8da27edd39 Add stream reuse fixes and performance improvements 2022-01-30 21:46:21 -07:00
Alec
c750bc1d92 Fixes 2022-01-30 21:18:07 -07:00
Alec Scott
cc4cd1c010 Initial commit of stream reuse 2022-01-30 19:23:35 -07:00
Alec Scott
4b1b189d5b Merge pull request #68 from repos-holder/main
Fix build errors for Windows
2022-01-30 12:48:19 -07:00
Artem Lukyanov
09f1134d1e Move enable command back to interface creation stage 2022-01-30 21:15:12 +03:00
Alec Scott
31cf03c10a Reconfigure Options on Windows 2022-01-28 09:50:47 -07:00
Alec Scott
1885e741fc Reconfigure options on Windows 2022-01-28 09:50:25 -07:00
Artem Lukyanov
03d0cc5453 Support down command for Windows 2022-01-28 17:57:04 +03:00
Artem Lukyanov
a27171ea62 Fix build errors for Windows 2022-01-28 11:24:09 +03:00
Alec Scott
c4a57651f2 Merge pull request #67 from jimt/patch-1
Fix broken link
2022-01-27 19:10:41 -07:00
Alec Scott
da9adb6f08 Remove MacOS dependency on ip 2022-01-27 19:03:55 -07:00
Alec Scott
a454f57fb0 Merge branch 'remove-ip-cmd' 2022-01-27 19:02:17 -07:00
Alec Scott
9d76c89aca Merge pull request #65 from sug0/remove-ip-cmd
Support Windows, Linux and presumably macOS
2022-01-27 19:01:45 -07:00
Alec Scott
f7664f3263 Standardize Tun creation and setup process 2022-01-27 18:56:18 -07:00
Jim Tittsler
6418a305aa Fix typos 2022-01-27 10:59:07 +09:00
Jim Tittsler
4e25bc9b36 Fix broken link 2022-01-25 13:06:27 +09:00
Tiago Carvalho
b1caae2d87 Platform specific tun code 2022-01-24 22:38:19 +00:00
Tiago Carvalho
48531ebdc2 Restore original tun.go code 2022-01-24 22:32:45 +00:00
Tiago Carvalho
663a66b2a2 Remove dependency on ip command 2022-01-24 22:32:45 +00:00
Artem Lukyanov
be276aef0b Add Windows support 2021-12-27 14:21:51 +03:00
Alec Scott
afc2118b98 Update README.md 2021-12-19 14:20:21 -07:00
Alec Scott
92c169dc9c Update README.md 2021-12-19 14:19:52 -07:00
Alec Scott
fa103eb1a1 Update Icon 2021-12-19 14:19:03 -07:00
Alec Scott
d1baa9fe1a Update Logo 2021-12-19 14:15:47 -07:00
Alec Scott
bba99213e0 Merge pull request #52 from hyprspace/feature/reduced-bandwidth
Improve Background Bandwidth Usage by Removing Discover Keys
2021-10-23 22:28:13 +00:00
Alec Scott
dd881949f0 Update README.md 2021-10-23 22:21:05 +00:00
Alec
c0469dd223 Update docs to reflect discovery key removal 2021-10-23 15:08:54 -07:00
Alec
414f80bd6b Improve background bandwidth usage by removing discover keys 2021-10-23 14:55:05 -07:00
Alec Scott
c81dcf21b5 Merge pull request #37 from notassigned/add-quic-transport
Add quic transport and ipv6
2021-09-22 12:13:41 -07:00
notassigned
b543729176 Added port check back in 2021-09-22 09:57:19 -04:00
Alec Scott
bef656a9e3 Create FUNDING.yml 2021-09-20 15:06:22 -07:00
notassigned
dc279408df Added in TCP transport to node create 2021-09-13 09:29:32 -04:00
notassigned
7ee920fb47 Fix typo in multiaddr 2021-09-11 21:20:01 -04:00
notassigned
723e4958c8 Use QUIC and fall back to TCP 2021-09-11 21:01:44 -04:00
notassigned
7a93823466 Replaced TCP with QUIC 2021-09-10 17:17:14 -04:00
Alec Scott
5ff46dc02e Merge pull request #27 from hyprspace/add/UPnP
Add initial support for UPnP to Hyprspace
2021-08-21 12:06:45 -07:00
Alec Scott
52c78391bb Add initial support for UPnP to Hyprspace 2021-08-21 12:04:47 -07:00
Alec Scott
c5ccc0daaa Update README.md 2021-07-22 09:56:38 -07:00
Alec Scott
795da55458 Update README.md 2021-07-22 09:54:27 -07:00
Alec Scott
5b957d3ed7 Create codeql-analysis.yml 2021-07-13 15:48:08 -07:00
Alec Scott
f9c74c1cc1 Merge pull request #19 from max-privatevoid/patch-1
Don't hardcode `ip` binary path
2021-07-08 18:54:11 -07:00
Max
17f09c48dc Don't hardcode ip binary path
Not all systems install the binary in this path, a correctly configured PATH environment variable should suffice.
2021-07-05 22:49:03 +02:00
Alec Scott
05cc6b08ce Merge pull request #15 from hyprspace/feature/dht-client
Switch to DHTClient Mode & Build Smaller Binaries
2021-06-26 10:36:58 -07:00
Alec Scott
1e23bdb2e0 Switch to DHTClient Mode & Build Smaller Binaries 2021-06-26 10:33:08 -07:00
Alec Scott
f4a7b5abbe Merge pull request #11 from hyprspace/feature/experience-improvements
Improve Error Handling & Allow Multiple Interfaces
2021-06-16 22:30:25 -07:00
Alec Scott
53768270bd Improve Error Handling & Allow Multiple Interfaces 2021-06-16 22:26:15 -07:00
Alec Scott
0c59a57fc0 Merge pull request #7 from swedneck/main
Add template systemd service file
2021-06-14 18:43:06 -07:00
Alec Scott
d3fb8478b9 Move systemd service file to examples directory 2021-06-14 18:41:08 -07:00
Tim Stahel
e8b9a09992 improved systemd service file, now a proper template 2021-06-14 00:38:05 +02:00
Tim Stahel
85a67189ff add crappy systemd service file 2021-06-12 21:34:14 +02:00
Alec Scott
91a21970ce Update README.md 2021-06-10 22:54:37 -07:00
Alec Scott
5b24c1d1bf Update README.md 2021-06-10 07:43:02 -07:00
Alec Scott
b5fe9d691d Update README.md 2021-06-09 22:28:30 -07:00
Alec Scott
76d27fbcfa Add Demo Video 2021-06-09 22:26:51 -07:00
Alec Scott
6eb4b47e37 Performance Fixes 2021-06-09 21:28:39 -07:00
Alec Scott
82ba8d357f Update README.md 2021-06-09 19:02:31 -07:00
Alec Scott
7601d9522a Add more detailed info about global variables. 2021-06-09 18:41:58 -07:00
Alec Scott
bb76c3dd8e Merge branch 'main' of https://github.com/hyprspace/hyprspace 2021-06-09 18:39:23 -07:00
Alec Scott
0fb0eb6c62 Fix up some more spelling 2021-06-09 18:39:19 -07:00
Alec Scott
e4a73a7154 Add Go Report Card 2021-06-09 18:37:15 -07:00
Alec Scott
021b1f861c Fix some more spelling across the board. 2021-06-09 18:34:23 -07:00
21 changed files with 1633 additions and 312 deletions

View File

@@ -1,12 +1,13 @@
#!/bin/bash
platforms=("linux/amd64" "linux/arm" "linux/arm64" "darwin/amd64" "darwin/arm64")
platforms=("linux/amd64" "linux/arm" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64" "windows/386")
for platform in "${platforms[@]}"
do
platform_split=(${platform//\// })
GOOS=${platform_split[0]}
GOARCH=${platform_split[1]}
env GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-X github.com/hyprspace/hyprspace/cli.appVersion=$1" -o hyprspace-$1-${GOOS}-${GOARCH} .
[ $GOOS == "windows" ] && EXT=".exe"
env GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/hyprspace/hyprspace/cli.appVersion=$1" -o hyprspace-$1-${GOOS}-${GOARCH}${EXT} .
done

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '21 19 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

1
.gitignore vendored
View File

@@ -1 +1,2 @@
hyprspace
.DS_Store

View File

@@ -1,7 +1,13 @@
<img src="hyprspace.png" width="300" height="300">
<img src="hyprspace.png" width="250">
# Hyprspace
A Lightweight VPN Built on top of Libp2p for Truly Distributed Networks.
[![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)
@@ -15,38 +21,48 @@ A Lightweight VPN Built on top of Libp2p for Truly Distributed Networks.
- [Tutorial](#tutorial)
## A Bit of Backstory
[Libp2p](libp2p.io) is a networking library created by [Protocol Labs](https://protocol.ai) that allows nodes to discover eachother 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.
[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, definetly go check it out! Wireguard actually inspired me to write Hyprspace. That said, although Wireguard is in a class of it's 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 cental 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 fradgile in the case that node goes down and you loose the whole network. So instead say that you want each node to be able to directly connect to eachother as they do in Hyprspace. Unfortunetly through Wireguard this would require every node to be publicly addressible which means manual port forwarding and no travelling nodes.
[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 eachother creating a stong reliable network even if they're all behind their own firewalls. No manual port forwarding required!
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 infastructure. 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.
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 a Mac you'll need to install `iproute2mac`. If you're using the `brew` package manager that's as simple as,
```bash
brew install iproute2mac
```
If you're running Hyprspace on Windows you'll need to install [tap-windows](http://build.openvpn.net/downloads/releases/).
### Installation
1. Go to Hyprspace Releases (other there -->)
#### 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 curl -L "PATH-TO-RELEASE" -o /usr/local/bin/hyprspace`
4. Run `sudo chmod a+x /usr/local/bin/hyprspace`
5. (Optional) Run `sudo ln -s /usr/local/bin/hyprspace /usr/bin/hyprspace`
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
@@ -58,7 +74,13 @@ brew install iproute2mac
| `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. |
| `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
@@ -71,6 +93,8 @@ 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
@@ -84,7 +108,7 @@ 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 eachother. By default Hyprspace will
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
@@ -120,20 +144,6 @@ 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`.
### Update our Discover Key
Looking in the interface's configuration you'll also notice a field called
`discover_key` (right above the interface's private key). It doesn't matter
which discovery key you pick but it much be the same for all of the nodes in your little cluster
so that they can find eachother.
(*Note you can use different `discover_key`s with different interfaces on the same
host to create different isolated networks.)
```yaml
discover_key: fiftieth-dandelion-wronged-craftwork
```
### Starting Up the Interfaces!
Now that we've got our configs all sorted we can start up the two interfaces!
@@ -158,7 +168,7 @@ pinging back and forth across the network.
ping 10.1.1.2
```
### Stoping the Interface and Cleaning Up
### Stopping the Interface and Cleaning Up
Now to stop the interface and clean up the system you can run,
###### Local Machine
@@ -173,9 +183,13 @@ and,
sudo hyprspace down hs1
```
## Disclaimer & Copyright
WireGuard is a registered trademark of Jason A. Donenfeld.
## License
Copyright 2021 Alec Scott <hi@alecbcs.com>
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.

View File

@@ -2,12 +2,15 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/tun"
)
// Pull downloads files from the Arken cluster.
// Down brings down a Hyprspace interface and removes it from the system.
var Down = cmd.Sub{
Name: "down",
Alias: "d",
@@ -16,16 +19,44 @@ var Down = cmd.Sub{
Run: DownRun,
}
// UpArgs handles the specific arguments for the up command.
// 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)
fmt.Println("[+] ip link delete dev " + args.InterfaceName)
err := tun.Delete(args.InterfaceName)
// 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

@@ -1,7 +1,7 @@
package cli
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
@@ -10,11 +10,10 @@ import (
"github.com/hyprspace/hyprspace/config"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/sethvargo/go-diceware/diceware"
"gopkg.in/yaml.v2"
)
// Pull downloads files from the Arken cluster.
// Init creates a configuration for a Hyprspace Interface.
var Init = cmd.Sub{
Name: "init",
Alias: "i",
@@ -28,6 +27,7 @@ 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)
@@ -39,25 +39,21 @@ func InitRun(r *cmd.Root, c *cmd.Sub) {
}
// Create New Libp2p Node
host, err := libp2p.New(context.Background())
host, err := libp2p.New()
checkErr(err)
// Get Node's Private Key
keyBytes, err := crypto.MarshalPrivateKey(host.Peerstore().PrivKey(host.ID()))
checkErr(err)
// Generate a random diceware discovery key
list, err := diceware.Generate(4)
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),
DiscoverKey: strings.Join(list, "-"),
},
}
@@ -74,5 +70,17 @@ func InitRun(r *cmd.Root, c *cmd.Sub) {
_, err = f.Write(out)
checkErr(err)
f.Close()
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

@@ -9,6 +9,8 @@ import (
"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."`
@@ -19,15 +21,18 @@ var Root *cmd.Root
func init() {
Root = &cmd.Root{
Name: "hypr",
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) {

331
cli/up.go
View File

@@ -1,13 +1,17 @@
package cli
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@@ -19,17 +23,21 @@ import (
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/songgao/water"
"golang.org/x/net/ipv4"
"github.com/nxadm/tail"
)
var (
Global config.Config
iface *water.Interface
RevLookup map[string]bool
// 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
)
// Pull downloads files from the Arken cluster.
// Up creates and brings up a Hyprspace Interface.
var Up = cmd.Sub{
Name: "up",
Alias: "up",
@@ -44,10 +52,12 @@ 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)
@@ -62,135 +72,296 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
}
// Read in configuration from file.
Global, err := config.Read(configPath)
cfg, err := config.Read(configPath)
checkErr(err)
if !flags.Foreground {
// Make results chan
out := make(chan bool)
go createDaemon(out)
select {
case <-out:
case <-time.After(30 * time.Second):
if err := createDaemon(cfg); err != nil {
fmt.Println("[+] Failed to Create Hyprspace Daemon")
fmt.Println(err)
} else {
fmt.Println("[+] Successfully Created Hyprspace Daemon")
}
checkErr(err)
fmt.Println("[+] Sucessfully Created Hyprspace Daemon")
return
}
// Setup reverse lookup hash map for authentication.
RevLookup = make(map[string]bool, len(Global.Peers))
for _, id := range Global.Peers {
RevLookup[id.ID] = true
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
iface, err = tun.New(Global.Interface.Name)
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)
// Set TUN MTU
tun.SetMTU(Global.Interface.Name, 1500)
// Add Address to Interface
tun.SetAddress(Global.Interface.Name, Global.Interface.Address)
}
// 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, Global.Interface.PrivateKey, streamHandler)
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 Global.Peers {
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, Global.Interface.DiscoverKey, peerTable)
go p2p.Discover(ctx, host, dht, peerTable)
go prettyDiscovery(ctx, host, peerTable)
go func() {
// Wait for a SIGINT or SIGTERM signal
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
fmt.Println("Received signal, shutting down...")
// Configure path for lock
lockPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".lock")
// Shut the node down
if err := host.Close(); err != nil {
panic(err)
}
os.Exit(0)
}()
// 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
tun.Up(Global.Interface.Name)
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
packet := make([]byte, 1500)
// + ----------------------------------------+
// | 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 {
plen, err := iface.Read(packet)
checkErr(err)
header, _ := ipv4.ParseHeader(packet[:plen])
_, ok := Global.Peers[header.Dst.String()]
if ok {
stream, err := host.NewStream(context.Background(), peerTable[header.Dst.String()], p2p.Protocol)
// Read in a packet from the tun device.
plen, err := tunDev.Iface.Read(packet)
if err != nil {
log.Println(err)
continue
}
go func() {
stream.Write(packet[:plen])
// 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
}
}
}
func createDaemon(out chan<- bool) {
// 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.
r, w, err := os.Pipe()
f, err := os.Create(logPath)
checkErr(err)
// Create Sub Process
process, err := os.StartProcess(
path,
append(os.Args, "--foreground"),
&os.ProcAttr{
Files: []*os.File{nil, w, nil},
Dir: ".",
Env: os.Environ(),
Files: []*os.File{nil, f, f},
},
)
checkErr(err)
scanner := bufio.NewScanner(r)
// Listen to the child process's log output to determine
// when the daemon is setup and connected to a set of peers.
count := 0
for scanner.Scan() && count < (4+len(Global.Peers)) {
fmt.Println(scanner.Text())
count++
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
}
fmt.Println(scanner.Text())
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)
out <- true
// 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
}
io.Copy(iface.ReadWriteCloser, stream)
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
@@ -200,14 +371,46 @@ func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]p
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 Sucessful. Network Ready.\n", ip)
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

@@ -14,8 +14,6 @@ import (
"github.com/tcnksm/go-latest"
)
var appVersion string
// 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{

View File

@@ -1,34 +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"`
Address string `yaml:"address"`
ID string `yaml:"id"`
DiscoverKey string `yaml:"discover_key"`
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 initalizes a config from a file.
func Read(path string) (result Config, err error) {
// Read initializes a config from a file.
func Read(path string) (*Config, error) {
in, err := os.ReadFile(path)
if err != nil {
return
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)
return
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

@@ -0,0 +1,14 @@
[Unit]
Description=hyprspace daemon for %i
After=network-online.target
Wants=network-online.target
Requires=network-online.target
[Service]
ExecStartPre=/usr/bin/sleep 10
ExecStart=/usr/local/bin/hyprspace up %i --foreground
ExecStop=/usr/local/bin/hyprspace down %i
Restart=on-failure
[Install]
WantedBy=default.target

20
go.mod
View File

@@ -4,19 +4,19 @@ go 1.16
require (
github.com/DataDrake/cli-ng/v2 v2.0.2
github.com/hashicorp/go-version v1.2.1 // indirect
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.14.1
github.com/libp2p/go-libp2p-core v0.8.5
github.com/libp2p/go-libp2p-discovery v0.5.0
github.com/libp2p/go-libp2p-kad-dht v0.12.1
github.com/multiformats/go-multiaddr v0.3.1
github.com/sethvargo/go-diceware v0.2.1
github.com/libp2p/go-libp2p v0.18.0
github.com/libp2p/go-libp2p-core v0.14.0
github.com/libp2p/go-libp2p-kad-dht v0.15.0
github.com/libp2p/go-libp2p-quic-transport v0.16.1
github.com/libp2p/go-tcp-transport v0.5.1
github.com/multiformats/go-multiaddr v0.5.0
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
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6
golang.org/x/sys v0.0.0-20210603125802-9665404d3644 // indirect
github.com/vishvananda/netlink v1.1.0
gopkg.in/yaml.v2 v2.4.0
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
)

824
go.sum

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 281 KiB

View File

@@ -2,22 +2,17 @@ package p2p
import (
"context"
"fmt"
"log"
"time"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
discovery "github.com/libp2p/go-libp2p-discovery"
dht "github.com/libp2p/go-libp2p-kad-dht"
)
func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous string, peerTable map[string]peer.ID) {
var routingDiscovery = discovery.NewRoutingDiscovery(dht)
discovery.Advertise(ctx, routingDiscovery, rendezvous)
ticker := time.NewTicker(time.Second * 1)
// 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 {
@@ -25,23 +20,14 @@ func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous str
case <-ctx.Done():
return
case <-ticker.C:
peers, err := discovery.FindPeers(ctx, routingDiscovery, rendezvous)
if err != nil {
log.Fatal(err)
}
for _, p := range peers {
if p.ID == h.ID() {
continue
}
if h.Network().Connectedness(p.ID) != network.Connected {
_, err = h.Network().DialPeer(ctx, p.ID)
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 {
fmt.Printf("Error Connecting To: %s\n", p.ID)
continue
}
}

View File

@@ -2,36 +2,49 @@ package p2p
import (
"context"
"errors"
"fmt"
"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"
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"
)
// Protocol is a descriptor for the Hyprspace P2P Protocol.
const Protocol = "/hyprspace/0.0.1"
type Packet struct {
Plen int
Data []byte
}
func CreateNode(ctx context.Context, inputKey string, handler network.StreamHandler) (node host.Host, dhtOut *dht.IpfsDHT, err error) {
// Unmarshall Private Key
// 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
}
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)
// Create libp2p node
node, err = libp2p.New(ctx,
libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", 8001)),
node, err = libp2p.New(
libp2p.ListenAddrStrings(ip6quic, ip4quic, ip6tcp, ip4tcp),
libp2p.Identity(privateKey),
libp2p.DefaultSecurity,
libp2p.NATPortMap(),
libp2p.DefaultMuxers,
libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(tcp.NewTCPTransport),
libp2p.FallbackDefaults,
)
if err != nil {
return
@@ -41,10 +54,7 @@ func CreateNode(ctx context.Context, inputKey string, handler network.StreamHand
node.SetStreamHandler(Protocol, handler)
// Create DHT Subsystem
dhtOut, err = dht.New(ctx, node)
if err != nil {
return
}
dhtOut = dht.NewDHTClient(ctx, node, datastore.NewMapDatastore())
// Define Bootstrap Nodes.
peers := []string{
@@ -78,6 +88,7 @@ func CreateNode(ctx context.Context, inputKey string, handler network.StreamHand
// 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 {
@@ -85,12 +96,18 @@ func CreateNode(ctx context.Context, inputKey string, handler network.StreamHand
defer wg.Done()
err := node.Connect(ctx, *peerInfo)
if err == nil {
lock.Lock()
count++
lock.Unlock()
}
}(peerInfo)
}
wg.Wait()
err = dhtOut.Bootstrap(ctx)
return
if count < 1 {
return node, dhtOut, errors.New("unable to bootstrap libp2p node")
}
return node, dhtOut, nil
}

28
tun/options.go Normal file
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

@@ -1,45 +1,26 @@
package tun
import (
"fmt"
"os/exec"
import "github.com/songgao/water"
"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
}
func New(name string) (result *water.Interface, err error) {
// Setup TUN Config
cfg := water.Config{
DeviceType: water.TUN,
// Apply configures the specified options for a TUN device.
func (t *TUN) Apply(opts ...Option) error {
for _, opt := range opts {
if opt == nil {
continue
}
cfg.Name = name
// Create TUN Interface
result, err = water.New(cfg)
return
}
func SetMTU(name string, mtu int) (err error) {
return ip("link", "set", "dev", name, "mtu", fmt.Sprintf("%d", mtu))
}
func SetAddress(name string, address string) (err error) {
return ip("addr", "add", address, "dev", name)
}
func Up(name string) (err error) {
return ip("link", "set", "dev", name, "up")
}
func Down(name string) (err error) {
return ip("link", "set", "dev", name, "down")
}
func Delete(name string) (err error) {
return ip("link", "delete", "dev", name)
}
func ip(args ...string) (err error) {
cmd := exec.Command("/sbin/ip", args...)
err = cmd.Run()
return
if err := opt(t); err != nil {
return err
}
}
return nil
}

72
tun/tun_darwin.go Normal file
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()
}

92
tun/tun_linux.go Normal file
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)
}

127
tun/tun_windows.go Normal file
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
}