31 Commits

Author SHA1 Message Date
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
16 changed files with 927 additions and 210 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 CGO_ENABLED=0 go build -ldflags "-s -w -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
done

View File

@@ -1,4 +1,4 @@
<img src="hyprspace.png" width="300" height="300">
<img src="hyprspace.png" width="250">
# Hyprspace
[![Go Report Card](https://goreportcard.com/badge/github.com/hyprspace/hyprspace)](https://goreportcard.com/report/github.com/hyprspace/hyprspace)
@@ -22,12 +22,12 @@ https://user-images.githubusercontent.com/19558067/121469777-f42cdb80-c971-11eb-
- [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 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.
[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 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 fragile 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 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!
@@ -45,10 +45,7 @@ If anyone else has some use cases please add them! Pull requests welcome!
## 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
@@ -88,6 +85,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
@@ -178,7 +177,7 @@ sudo hyprspace down hs1
## 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,6 +2,9 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/hyprspace/hyprspace/tun"
@@ -26,7 +29,34 @@ 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
// suceeded.
if err0 != nil && err1 != nil {
checkErr(err0)
checkErr(err1)
}
fmt.Println("[+] deleted hyprspace " + args.InterfaceName + " daemon")
}

View File

@@ -1,7 +1,6 @@
package cli
import (
"context"
"os"
"path/filepath"
@@ -38,7 +37,7 @@ 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

201
cli/up.go
View File

@@ -3,13 +3,15 @@ package cli
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
@@ -22,17 +24,17 @@ 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"
)
var (
// iface is the tun device used to pass packets between
// Hyprspace and the user's machine.
iface *water.Interface
tunDev *tun.TUN
// RevLookup allow quick lookups of an incoming stream
// for security before accepting or responding to any data.
RevLookup map[string]bool
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.
@@ -91,21 +93,42 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
}
// Setup reverse lookup hash map for authentication.
RevLookup = make(map[string]bool, len(cfg.Peers))
for _, id := range cfg.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")
// Create new TUN device
iface, err = tun.New(cfg.Interface.Name)
if err != nil {
checkErr(errors.New("interface already in use"))
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)
}
// Set TUN MTU
tun.SetMTU(cfg.Interface.Name, 1420)
// Add Address to Interface
tun.SetAddress(cfg.Interface.Name, cfg.Interface.Address)
// Setup System Context
ctx := context.Background()
@@ -113,34 +136,16 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
fmt.Println("[+] Creating LibP2P Node")
// Check that the listener port is available.
var ln net.Listener
port := cfg.Interface.ListenPort
if port != 8001 {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
checkErr(errors.New("could not create node, listen port already in use by something else"))
}
} else {
for {
ln, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err == nil {
break
}
if port >= 65535 {
checkErr(errors.New("failed to find open port"))
}
port++
}
}
if ln != nil {
ln.Close()
}
port, err := verifyPort(cfg.Interface.ListenPort)
checkErr(err)
// Create P2P Node
host, dht, err := p2p.CreateNode(ctx,
host, dht, err := p2p.CreateNode(
ctx,
cfg.Interface.PrivateKey,
port,
streamHandler)
streamHandler,
)
checkErr(err)
// Setup Peer Table for Quick Packet --> Dest ID lookup
@@ -155,6 +160,9 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
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")
go func() {
// Wait for a SIGINT or SIGTERM signal
ch := make(chan os.Signal, 1)
@@ -166,36 +174,70 @@ func UpRun(r *cmd.Root, c *cmd.Sub) {
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.
err = os.WriteFile(lockPath, []byte(fmt.Sprint(os.Getpid())), os.ModePerm)
checkErr(err)
// Bring Up TUN Device
tun.Up(cfg.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, 1420)
var stream network.Stream
var header *ipv4.Header
var plen int
activeStreams = make(map[string]network.Stream)
var packet = make([]byte, 1420)
for {
plen, err = iface.Read(packet)
checkErr(err)
header, _ = ipv4.ParseHeader(packet)
_, ok := cfg.Peers[header.Dst.String()]
plen, err := tunDev.Iface.Read(packet)
if err != nil {
log.Println(err)
continue
}
dst := net.IPv4(packet[16], packet[17], packet[18], packet[19]).String()
stream, ok := activeStreams[dst]
if ok {
stream, err = host.NewStream(ctx, peerTable[header.Dst.String()], p2p.Protocol)
err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err == nil {
_, err = stream.Write(packet[:plen])
if err == nil {
continue
}
}
stream.Close()
delete(activeStreams, dst)
ok = false
}
if peer, ok := peerTable[dst]; ok {
stream, err = host.NewStream(ctx, peer, p2p.Protocol)
if err != nil {
log.Println(err)
continue
}
stream.Write(packet[:plen])
stream.Close()
err = binary.Write(stream, binary.LittleEndian, uint16(plen))
if err != nil {
stream.Close()
continue
}
_, err = stream.Write(packet[:plen])
if err != nil {
stream.Close()
continue
}
activeStreams[dst] = stream
}
}
}
func createDaemon(cfg config.Config, out chan<- error) {
func createDaemon(cfg *config.Config, out chan<- error) {
path, err := os.Executable()
checkErr(err)
// Create Pipe to monitor for daemon output.
@@ -218,6 +260,8 @@ func createDaemon(cfg config.Config, out chan<- error) {
count++
}
}
// Release the created daemon
err = process.Release()
checkErr(err)
if count < len(cfg.Peers) {
@@ -230,9 +274,28 @@ 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 {
_, err := stream.Read(packetSize)
if err != nil {
stream.Close()
return
}
size := binary.LittleEndian.Uint16(packetSize)
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])
}
io.Copy(iface.ReadWriteCloser, stream)
stream.Close()
}
func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]peer.ID) {
@@ -256,3 +319,29 @@ func prettyDiscovery(ctx context.Context, node host.Host, peerTable map[string]p
}
}
}
func verifyPort(port int) (int, error) {
var ln net.Listener
var err error
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 {
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

@@ -8,6 +8,7 @@ import (
// 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"`
}
@@ -27,12 +28,12 @@ type Peer struct {
}
// Read initializes a config from a file.
func Read(path string) (result Config, err error) {
func Read(path string) (*Config, error) {
in, err := os.ReadFile(path)
if err != nil {
return
return nil, err
}
result = Config{
result := Config{
Interface: Interface{
Name: "hs0",
ListenPort: 8001,
@@ -41,6 +42,14 @@ func Read(path string) (result Config, err error) {
PrivateKey: "",
},
}
// Read in config settings from file.
err = yaml.Unmarshal(in, &result)
return
if err != nil {
return nil, err
}
// Overwrite path of config to input.
result.Path = path
return &result, nil
}

View File

@@ -2,8 +2,10 @@
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

20
go.mod
View File

@@ -4,20 +4,18 @@ 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.4.5
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.6
github.com/libp2p/go-libp2p-kad-dht v0.12.1
github.com/libp2p/go-libp2p-quic-transport v0.12.0
github.com/libp2p/go-tcp-transport v0.2.8
github.com/multiformats/go-multiaddr v0.3.3
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/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
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
)

461
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: 604 KiB

View File

@@ -36,11 +36,12 @@ func CreateNode(ctx context.Context, inputKey string, port int, handler network.
ip4tcp := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port)
// Create libp2p node
node, err = libp2p.New(ctx,
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,

21
tun/options.go Normal file
View File

@@ -0,0 +1,21 @@
package tun
type Option func(tun *TUN) error
func Address(address string) Option {
return func(tun *TUN) error {
return tun.setAddress(address)
}
}
func MTU(mtu int) Option {
return func(tun *TUN) error {
return tun.setMTU(mtu)
}
}
func DestAddress(address string) Option {
return func(tun *TUN) error {
return tun.setDestAddress(address)
}
}

View File

@@ -1,52 +1,22 @@
package tun
import (
"fmt"
"os/exec"
import "github.com/songgao/water"
"github.com/songgao/water"
)
type TUN struct {
Iface *water.Interface
MTU int
Src string
Dst string
}
// New creates and returns a new TUN interface for the application.
func New(name string) (result *water.Interface, err error) {
// Setup TUN Config
cfg := water.Config{
DeviceType: water.TUN,
func (t *TUN) Apply(opts ...Option) error {
for _, opt := range opts {
if opt == nil {
continue
}
if err := opt(t); err != nil {
return err
}
}
cfg.Name = name
// Create TUN Interface
result, err = water.New(cfg)
return
}
// SetMTU sets the Maximum Tansmission Unit Size for a
// Packet on the interface.
func SetMTU(name string, mtu int) (err error) {
return ip("link", "set", "dev", name, "mtu", fmt.Sprintf("%d", mtu))
}
// SetAddress sets the interface's known address and subnet.
func SetAddress(name string, address string) (err error) {
return ip("addr", "add", address, "dev", name)
}
// Up brings up an interface to allow it to start accepting connections.
func Up(name string) (err error) {
return ip("link", "set", "dev", name, "up")
}
// Down brings down an interface stopping active connections.
func Down(name string) (err error) {
return ip("link", "set", "dev", name, "down")
}
// Delete removes a TUN device from the host.
func Delete(name string) (err error) {
return ip("link", "delete", "dev", name)
}
func ip(args ...string) (err error) {
cmd := exec.Command("ip", args...)
err = cmd.Run()
return
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
}