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
This commit is contained in:
Alec Scott
2022-02-03 09:08:47 -07:00
committed by GitHub
parent d5a300a8a9
commit 43d0df3731
5 changed files with 143 additions and 47 deletions

View File

@@ -4,7 +4,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/hyprspace/hyprspace)](https://goreportcard.com/report/github.com/hyprspace/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) [![](https://img.shields.io/matrix/hyprspace:matrix.org)](https://matrix.to/#/%23hyprspace:matrix.org)
A Lightweight VPN Built on top of Libp2p for Truly Distributed Networks. A Lightweight VPN Built on top of IPFS & Libp2p for Truly Distributed Networks.
https://user-images.githubusercontent.com/19558067/121469777-f42cdb80-c971-11eb-84de-9dd4f6d6cd1f.mp4 https://user-images.githubusercontent.com/19558067/121469777-f42cdb80-c971-11eb-84de-9dd4f6d6cd1f.mp4
@@ -27,9 +27,9 @@ https://user-images.githubusercontent.com/19558067/121469777-f42cdb80-c971-11eb-
**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. **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? ### 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. [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 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: ## Use Cases:
##### A Digital Nomad ##### A Digital Nomad
@@ -44,7 +44,9 @@ If anyone else has some use cases please add them! Pull requests welcome!
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## Getting Started ## Getting Started
### Prerequisites ### Prerequisites
If you're running Hyprspace on Windows you'll need to install [tap-windows](http://build.openvpn.net/downloads/releases/). If you're running Hyprspace on Windows you'll need to install [tap-windows](http://build.openvpn.net/downloads/releases/).
### Installation ### Installation
@@ -175,6 +177,10 @@ and,
sudo hyprspace down hs1 sudo hyprspace down hs1
``` ```
## Disclaimer & Copyright
WireGuard is a registered trademark of Jason A. Donenfeld.
## License ## License
Copyright 2021-2022 Alec Scott <hi@alecbcs.com> Copyright 2021-2022 Alec Scott <hi@alecbcs.com>

View File

@@ -1,8 +1,10 @@
package cli package cli
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/DataDrake/cli-ng/v2/cmd" "github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/config" "github.com/hyprspace/hyprspace/config"
@@ -68,5 +70,17 @@ func InitRun(r *cmd.Root, c *cmd.Sub) {
_, err = f.Write(out) _, err = f.Write(out)
checkErr(err) 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()
} }

150
cli/up.go
View File

@@ -1,7 +1,6 @@
package cli package cli
import ( import (
"bufio"
"context" "context"
"encoding/binary" "encoding/binary"
"errors" "errors"
@@ -24,6 +23,7 @@ import (
"github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/nxadm/tail"
) )
var ( var (
@@ -76,16 +76,9 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
checkErr(err) checkErr(err)
if !flags.Foreground { if !flags.Foreground {
// Make results chan if err := createDaemon(cfg); err != nil {
out := make(chan error)
go createDaemon(cfg, out)
select {
case err = <-out:
case <-time.After(30 * time.Second):
}
if err != nil {
fmt.Println("[+] Failed to Create Hyprspace Daemon") fmt.Println("[+] Failed to Create Hyprspace Daemon")
fmt.Println(err)
} else { } else {
fmt.Println("[+] Successfully Created Hyprspace Daemon") fmt.Println("[+] Successfully Created Hyprspace Daemon")
} }
@@ -156,6 +149,7 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
} }
fmt.Println("[+] Setting Up Node Discovery via DHT") fmt.Println("[+] Setting Up Node Discovery via DHT")
// Setup P2P Discovery // Setup P2P Discovery
go p2p.Discover(ctx, host, dht, peerTable) go p2p.Discover(ctx, host, dht, peerTable)
go prettyDiscovery(ctx, host, peerTable) go prettyDiscovery(ctx, host, peerTable)
@@ -163,25 +157,8 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
// Configure path for lock // Configure path for lock
lockPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".lock") lockPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".lock")
go func() { // Register the application to listen for SIGINT/SIGTERM
// Wait for a SIGINT or SIGTERM signal go signalExit(host, lockPath)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
fmt.Println("Received signal, shutting down...")
// Shut the node down
if err := host.Close(); err != nil {
panic(err)
}
// Remove daemon lock from file system.
err = os.Remove(lockPath)
checkErr(err)
// Exit the application.
os.Exit(0)
}()
// Write lock to filesystem to indicate an existing running daemon. // Write lock to filesystem to indicate an existing running daemon.
err = os.WriteFile(lockPath, []byte(fmt.Sprint(os.Getpid())), os.ModePerm) err = os.WriteFile(lockPath, []byte(fmt.Sprint(os.Getpid())), os.ModePerm)
@@ -194,80 +171,156 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
} }
fmt.Println("[+] Network Setup Complete...Waiting on Node Discovery") fmt.Println("[+] Network Setup Complete...Waiting on Node Discovery")
// Listen For New Packets on TUN Interface
// + ----------------------------------------+
// | Listen For New Packets on TUN Interface |
// + ----------------------------------------+
// Initialize active streams map and packet byte array.
activeStreams = make(map[string]network.Stream) activeStreams = make(map[string]network.Stream)
var packet = make([]byte, 1420) var packet = make([]byte, 1420)
for { for {
// Read in a packet from the tun device.
plen, err := tunDev.Iface.Read(packet) plen, err := tunDev.Iface.Read(packet)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
continue continue
} }
// Decode the packet's destination address
dst := net.IPv4(packet[16], packet[17], packet[18], packet[19]).String() 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] stream, ok := activeStreams[dst]
if ok { 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)) err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err == nil { 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]) _, err = stream.Write(packet[:plen])
if err == nil { if err == nil {
continue 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() stream.Close()
delete(activeStreams, dst) delete(activeStreams, dst)
ok = false
} }
// Check if the destination of the packet is a known peer to
// the interface.
if peer, ok := peerTable[dst]; ok { if peer, ok := peerTable[dst]; ok {
stream, err = host.NewStream(ctx, peer, p2p.Protocol) stream, err = host.NewStream(ctx, peer, p2p.Protocol)
if err != nil { if err != nil {
continue continue
} }
// Write packet length
err = binary.Write(stream, binary.LittleEndian, uint16(plen)) err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err != nil { if err != nil {
stream.Close() stream.Close()
continue continue
} }
// Write the packet
_, err = stream.Write(packet[:plen]) _, err = stream.Write(packet[:plen])
if err != nil { if err != nil {
stream.Close() stream.Close()
continue 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 activeStreams[dst] = stream
} }
} }
} }
func createDaemon(cfg *config.Config, out chan<- error) { // 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() path, err := os.Executable()
checkErr(err) checkErr(err)
// Generate log path
logPath := filepath.Join(filepath.Dir(cfg.Path), cfg.Interface.Name+".log")
// Create Pipe to monitor for daemon output. // Create Pipe to monitor for daemon output.
r, w, err := os.Pipe() f, err := os.Create(logPath)
checkErr(err) checkErr(err)
// Create Sub Process // Create Sub Process
process, err := os.StartProcess( process, err := os.StartProcess(
path, path,
append(os.Args, "--foreground"), append(os.Args, "--foreground"),
&os.ProcAttr{ &os.ProcAttr{
Files: []*os.File{nil, w, w}, Dir: ".",
Env: os.Environ(),
Files: []*os.File{nil, f, f},
}, },
) )
checkErr(err) 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 count := 0
for count < len(cfg.Peers) && scanner.Scan() { deadlineHit := false
fmt.Println(scanner.Text()) countChan := make(chan int)
if strings.HasPrefix(scanner.Text(), "[+] Connection to") { go func(out chan<- int) {
count++ 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++
}
}
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 // Release the created daemon
err = process.Release() err = process.Release()
checkErr(err) checkErr(err)
if count < len(cfg.Peers) {
out <- errors.New("failed to create daemon") // Check if the daemon exited prematurely
if !deadlineHit && count < len(cfg.Peers) {
return errors.New("failed to create daemon")
} }
out <- nil return nil
} }
func streamHandler(stream network.Stream) { func streamHandler(stream network.Stream) {
@@ -279,12 +332,17 @@ func streamHandler(stream network.Stream) {
var packet = make([]byte, 1420) var packet = make([]byte, 1420)
var packetSize = make([]byte, 2) var packetSize = make([]byte, 2)
for { for {
// Read the incoming packet's size as a binary value.
_, err := stream.Read(packetSize) _, err := stream.Read(packetSize)
if err != nil { if err != nil {
stream.Close() stream.Close()
return return
} }
// Decode the incoming packet's size from binary.
size := binary.LittleEndian.Uint16(packetSize) size := binary.LittleEndian.Uint16(packetSize)
// Read in the packet until completion.
var plen uint16 = 0 var plen uint16 = 0
for plen < size { for plen < size {
tmp, err := stream.Read(packet[plen:size]) tmp, err := stream.Read(packet[plen:size])
@@ -299,6 +357,8 @@ func streamHandler(stream network.Stream) {
} }
func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]peer.ID) { 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)) tempTable := make(map[string]peer.ID, len(peerTable))
for ip, id := range peerTable { for ip, id := range peerTable {
tempTable[ip] = id tempTable[ip] = id
@@ -308,6 +368,7 @@ func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]p
stream, err := node.NewStream(ctx, id, p2p.Protocol) stream, err := node.NewStream(ctx, id, p2p.Protocol)
if err != nil && (strings.HasPrefix(err.Error(), "failed to dial") || if err != nil && (strings.HasPrefix(err.Error(), "failed to dial") ||
strings.HasPrefix(err.Error(), "no addresses")) { strings.HasPrefix(err.Error(), "no addresses")) {
// Attempt to connect to peers slowly when they aren't found.
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
continue continue
} }
@@ -323,12 +384,17 @@ func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]p
func verifyPort(port int) (int, error) { func verifyPort(port int) (int, error) {
var ln net.Listener var ln net.Listener
var err error var err error
// If a user manually sets a port don't try to automatically
// find an open port.
if port != 8001 { if port != 8001 {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port)) ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil { if err != nil {
return port, errors.New("could not create node, listen port already in use by something else") return port, errors.New("could not create node, listen port already in use by something else")
} }
} else { } else {
// Automatically look for an open port when a custom port isn't
// selected by a user.
for { for {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port)) ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err == nil { if err == nil {

View File

@@ -1,6 +1,8 @@
package config package config
import ( import (
"fmt"
"net"
"os" "os"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -37,7 +39,7 @@ func Read(path string) (*Config, error) {
Interface: Interface{ Interface: Interface{
Name: "hs0", Name: "hs0",
ListenPort: 8001, ListenPort: 8001,
Address: "10.1.1.1", Address: "10.1.1.1/24",
ID: "", ID: "",
PrivateKey: "", PrivateKey: "",
}, },
@@ -49,6 +51,13 @@ func Read(path string) (*Config, error) {
return nil, err 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. // Overwrite path of config to input.
result.Path = path result.Path = path
return &result, nil return &result, nil

1
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/libp2p/go-libp2p-quic-transport v0.15.2 github.com/libp2p/go-libp2p-quic-transport v0.15.2
github.com/libp2p/go-tcp-transport v0.4.0 github.com/libp2p/go-tcp-transport v0.4.0
github.com/multiformats/go-multiaddr v0.4.1 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/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.1.0