commit
227ed206a4
56
README.md
56
README.md
@ -61,10 +61,12 @@ Main configuration example:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"ExternalIP": "198.51.100.2",
|
"ExternalIP": "198.51.100.2",
|
||||||
|
"ExternalIP6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||||
"ListenPort": 51820,
|
"ListenPort": 51820,
|
||||||
"Domain": "dsnet",
|
"Domain": "dsnet",
|
||||||
"InterfaceName": "dsnet",
|
"InterfaceName": "dsnet",
|
||||||
"Network": "10.164.236.0/22",
|
"Network": "10.164.236.0/22",
|
||||||
|
"Network6": "fd00:7b31:106a:ae00::/64",
|
||||||
"IP": "10.164.236.1",
|
"IP": "10.164.236.1",
|
||||||
"DNS": "",
|
"DNS": "",
|
||||||
"Networks": [],
|
"Networks": [],
|
||||||
@ -76,6 +78,7 @@ Main configuration example:
|
|||||||
"Owner": "naggie",
|
"Owner": "naggie",
|
||||||
"Description": "Home server",
|
"Description": "Home server",
|
||||||
"IP": "10.164.236.2",
|
"IP": "10.164.236.2",
|
||||||
|
"IP6": "fd00:7b31:106a:ae00:44c3:29c3:53b1:a6f9",
|
||||||
"Added": "2020-05-07T10:04:46.336286992+01:00",
|
"Added": "2020-05-07T10:04:46.336286992+01:00",
|
||||||
"Networks": [],
|
"Networks": [],
|
||||||
"PublicKey": "altJeQ/V52JZQrGcA9RiKcpZusYU6zMUJhl7Wbd9rX0=",
|
"PublicKey": "altJeQ/V52JZQrGcA9RiKcpZusYU6zMUJhl7Wbd9rX0=",
|
||||||
@ -88,11 +91,11 @@ Explanation of each field:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"ExternalIP": "198.51.100.2",
|
"ExternalIP": "198.51.100.2",
|
||||||
|
"ExternalIP6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||||
|
|
||||||
This is the external IP that will be the value of Endpoint for the server peer
|
This is the external IP that will be the value of Endpoint for the server peer
|
||||||
in client configs. It is automatically detected by opening a socket or using an
|
in client configs. It is automatically detected by opening a socket or using an
|
||||||
external IP discovery service -- the first to give a valid public IPv4 will
|
external IP discovery service -- the first to give a valid public IP will win.
|
||||||
win.
|
|
||||||
|
|
||||||
|
|
||||||
"ListenPort": 51820,
|
"ListenPort": 51820,
|
||||||
@ -110,6 +113,7 @@ connection by polling the report file.
|
|||||||
The wireguard interface name.
|
The wireguard interface name.
|
||||||
|
|
||||||
"Network": "10.164.236.0/22",
|
"Network": "10.164.236.0/22",
|
||||||
|
"Network6": "fd00:7b31:106a:ae00::/64",
|
||||||
|
|
||||||
The CIDR network to use when allocating IPs to peers. This subnet, a `/22` in
|
The CIDR network to use when allocating IPs to peers. This subnet, a `/22` in
|
||||||
the `10.0.0.0/16` block is generated randomly to (probably) avoid collisions
|
the `10.0.0.0/16` block is generated randomly to (probably) avoid collisions
|
||||||
@ -117,7 +121,10 @@ with other networks. There are 1022 addresses available. Addresses are
|
|||||||
allocated to peers when peers are added with `dsnet add` using the lowest
|
allocated to peers when peers are added with `dsnet add` using the lowest
|
||||||
available address.
|
available address.
|
||||||
|
|
||||||
|
A random ULA network with a subnet of 0 is generated for IPv6.
|
||||||
|
|
||||||
"IP": "10.164.236.1",
|
"IP": "10.164.236.1",
|
||||||
|
"IP6": "fd00:7b31:106a:ae00:44c3:29c3:53b1:a6f9",
|
||||||
|
|
||||||
This is the private VPN IP of the server peer. It is the first address in the
|
This is the private VPN IP of the server peer. It is the first address in the
|
||||||
above pool.
|
above pool.
|
||||||
@ -261,11 +268,54 @@ See
|
|||||||
[etc/README.md](https://github.com/naggie/dsnet/blob/master/contrib/report_rendering/README.md)
|
[etc/README.md](https://github.com/naggie/dsnet/blob/master/contrib/report_rendering/README.md)
|
||||||
for hugo and PHP code for rendering a similar table.
|
for hugo and PHP code for rendering a similar table.
|
||||||
|
|
||||||
|
# Generating other config files
|
||||||
|
|
||||||
|
dsnet currently supports the generation of `wg-quick` configuration by default.
|
||||||
|
It can also generate VyOS/Vyatta configuration for EdgeOS/Unifi devices such as
|
||||||
|
the Edgerouter 4 using the
|
||||||
|
[wireguard-vyatta](https://github.com/WireGuard/wireguard-vyatta-ubnt) package.
|
||||||
|
|
||||||
|
To change the config file format, set the following environment variables:
|
||||||
|
|
||||||
|
* `DSNET_OUTPUT=vyatta`
|
||||||
|
* `DSNET_OUTPUT=wg-quick`
|
||||||
|
|
||||||
|
Example vyatta output:
|
||||||
|
|
||||||
|
configure
|
||||||
|
set interfaces wireguard wg0 address 10.165.52.3/22
|
||||||
|
set interfaces wireguard wg0 address fd00:7b31:106a:ae00:f7bb:bf31:201f:60ab/64
|
||||||
|
set interfaces wireguard wg0 route-allowed-ips true
|
||||||
|
set interfaces wireguard wg0 private-key cAtj1tbjGGmVoxdY78q9Sv0EgNlawbzffGWjajQkLFw=
|
||||||
|
set interfaces wireguard wg0 description dsnet
|
||||||
|
|
||||||
|
set interfaces wireguard wg0 peer PjxQM7OwVYvOJfORA1EluLw8CchSu7jLq92YYJi5ohY= endpoint 123.123.123.123:51820
|
||||||
|
set interfaces wireguard wg0 peer PjxQM7OwVYvOJfORA1EluLw8CchSu7jLq92YYJi5ohY= persistent-keepalive 25
|
||||||
|
set interfaces wireguard wg0 peer PjxQM7OwVYvOJfORA1EluLw8CchSu7jLq92YYJi5ohY= preshared-key w1FtOKoMEdnhsjREtSvpg1CHEKFzFzJWaQYZwaUCV38=
|
||||||
|
set interfaces wireguard wg0 peer PjxQM7OwVYvOJfORA1EluLw8CchSu7jLq92YYJi5ohY= allowed-ips 10.165.52.0/22
|
||||||
|
set interfaces wireguard wg0 peer PjxQM7OwVYvOJfORA1EluLw8CchSu7jLq92YYJi5ohY= allowed-ips fd00:7b31:106a:ae00::/64
|
||||||
|
commit; save
|
||||||
|
|
||||||
|
Replace `wg0` with an unused interface name in the range `wg0-wg999`.
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
> Does dsnet support IPv6?
|
> Does dsnet support IPv6?
|
||||||
|
|
||||||
Not currently but this is a [planned feature](https://github.com/naggie/dsnet/issues/1).
|
Yes! By default since version 0.2, a random ULA subnet is generated with a 0
|
||||||
|
subnet ID. Peers are allocated random addresses when added. Existing IPv4
|
||||||
|
configs will not be updated -- add a `Network6` subnet to the existing config
|
||||||
|
to allocate addresses to new peers.
|
||||||
|
|
||||||
|
Like IPv4, it's up to you if you want to provide NAT IPv6 access to the
|
||||||
|
internet; alternatively (and preferably) you can allocate a a real IPv6 subnet
|
||||||
|
such that all peers have a real globally routeable IPv6 address.
|
||||||
|
|
||||||
|
Upon initialisation, the server IPv4 and IPv6 external IP addresses are
|
||||||
|
discovered on a best-effort basis. Clients will have configuration configured
|
||||||
|
for the server IPv4 preferentially. If not IPv4 is configured, IPv6 is used;
|
||||||
|
this is to give the best chance of the VPN working regardless of the dodgy
|
||||||
|
network you're on.
|
||||||
|
|
||||||
> Is dsnet production ready?
|
> Is dsnet production ready?
|
||||||
|
|
||||||
|
76
add.go
76
add.go
@ -3,40 +3,73 @@ package dsnet
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const wgQuickPeerConf = `[Interface]
|
const wgQuickPeerConf = `[Interface]
|
||||||
Address = {{ .Peer.IP }}/22
|
{{ if gt (.DsnetConfig.Network.IPNet.IP | len) 0 -}}
|
||||||
|
Address={{ .Peer.IP }}/{{ .CidrSize }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ if gt (.DsnetConfig.Network6.IPNet.IP | len) 0 -}}
|
||||||
|
Address={{ .Peer.IP6 }}/{{ .CidrSize6 }}
|
||||||
|
{{ end -}}
|
||||||
PrivateKey={{ .Peer.PrivateKey.Key }}
|
PrivateKey={{ .Peer.PrivateKey.Key }}
|
||||||
{{- if .DsnetConfig.DNS }}
|
{{- if .DsnetConfig.DNS }}
|
||||||
DNS = {{ .DsnetConfig.DNS }}
|
DNS={{ .DsnetConfig.DNS }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey={{ .DsnetConfig.PrivateKey.PublicKey.Key }}
|
PublicKey={{ .DsnetConfig.PrivateKey.PublicKey.Key }}
|
||||||
PresharedKey={{ .Peer.PresharedKey.Key }}
|
PresharedKey={{ .Peer.PresharedKey.Key }}
|
||||||
|
{{ if gt (.DsnetConfig.ExternalIP | len) 0 -}}
|
||||||
Endpoint={{ .DsnetConfig.ExternalIP }}:{{ .DsnetConfig.ListenPort }}
|
Endpoint={{ .DsnetConfig.ExternalIP }}:{{ .DsnetConfig.ListenPort }}
|
||||||
AllowedIPs={{ .AllowedIPs }}
|
{{ else -}}
|
||||||
|
Endpoint={{ .DsnetConfig.ExternalIP6 }}:{{ .DsnetConfig.ListenPort }}
|
||||||
|
{{ end -}}
|
||||||
PersistentKeepalive={{ .Keepalive }}
|
PersistentKeepalive={{ .Keepalive }}
|
||||||
|
{{ if gt (.DsnetConfig.Network.IPNet.IP | len) 0 -}}
|
||||||
|
AllowedIPs={{ .DsnetConfig.Network }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ if gt (.DsnetConfig.Network6.IPNet.IP | len) 0 -}}
|
||||||
|
AllowedIPs={{ .DsnetConfig.Network6 }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ range .DsnetConfig.Networks -}}
|
||||||
|
AllowedIPs={{ . }}
|
||||||
|
{{ end -}}
|
||||||
`
|
`
|
||||||
|
|
||||||
// TODO use random wg0-wg999 to hopefully avoid conflict by default?
|
// TODO use random wg0-wg999 to hopefully avoid conflict by default?
|
||||||
const vyattaPeerConf = `configure
|
const vyattaPeerConf = `configure
|
||||||
set interfaces wireguard wg0 address {{ .Peer.IP }}/{{ .Cidrmask }}
|
{{ if gt (.DsnetConfig.Network.IPNet.IP | len) 0 -}}
|
||||||
|
set interfaces wireguard wg0 address {{ .Peer.IP }}/{{ .CidrSize }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ if gt (.DsnetConfig.Network6.IPNet.IP | len) 0 -}}
|
||||||
|
set interfaces wireguard wg0 address {{ .Peer.IP6 }}/{{ .CidrSize6 }}
|
||||||
|
{{ end -}}
|
||||||
set interfaces wireguard wg0 route-allowed-ips true
|
set interfaces wireguard wg0 route-allowed-ips true
|
||||||
set interfaces wireguard wg0 private-key {{ .Peer.PrivateKey.Key }}
|
set interfaces wireguard wg0 private-key {{ .Peer.PrivateKey.Key }}
|
||||||
set interfaces wireguard wg0 description {{ conf.InterfaceName }}
|
set interfaces wireguard wg0 description {{ .DsnetConfig.InterfaceName }}
|
||||||
{{- if .DsnetConfig.DNS }}
|
{{- if .DsnetConfig.DNS }}
|
||||||
#set service dns forwarding name-server {{ .DsnetConfig.DNS }}
|
#set service dns forwarding name-server {{ .DsnetConfig.DNS }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if gt (.DsnetConfig.ExternalIP | len) 0 -}}
|
||||||
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} endpoint {{ .DsnetConfig.ExternalIP }}:{{ .DsnetConfig.ListenPort }}
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} endpoint {{ .DsnetConfig.ExternalIP }}:{{ .DsnetConfig.ListenPort }}
|
||||||
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} allowed-ips {{ .AllowedIPs }}
|
{{ else -}}
|
||||||
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} endpoint {{ .DsnetConfig.ExternalIP6 }}:{{ .DsnetConfig.ListenPort }}
|
||||||
|
{{ end -}}
|
||||||
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} persistent-keepalive {{ .Keepalive }}
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} persistent-keepalive {{ .Keepalive }}
|
||||||
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} preshared-key {{ .Peer.PresharedKey.Key }}
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} preshared-key {{ .Peer.PresharedKey.Key }}
|
||||||
|
{{ if gt (.DsnetConfig.Network.IPNet.IP | len) 0 -}}
|
||||||
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} allowed-ips {{ .DsnetConfig.Network }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ if gt (.DsnetConfig.Network6.IPNet.IP | len) 0 -}}
|
||||||
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} allowed-ips {{ .DsnetConfig.Network6 }}
|
||||||
|
{{ end -}}
|
||||||
|
{{ range .DsnetConfig.Networks -}}
|
||||||
|
set interfaces wireguard wg0 peer {{ .DsnetConfig.PrivateKey.PublicKey.Key }} allowed-ips {{ . }}
|
||||||
|
{{ end -}}
|
||||||
commit; save
|
commit; save
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -62,8 +95,6 @@ func Add() {
|
|||||||
privateKey := GenerateJSONPrivateKey()
|
privateKey := GenerateJSONPrivateKey()
|
||||||
publicKey := privateKey.PublicKey()
|
publicKey := privateKey.PublicKey()
|
||||||
|
|
||||||
IP := conf.MustAllocateIP()
|
|
||||||
|
|
||||||
peer := PeerConfig{
|
peer := PeerConfig{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
@ -72,10 +103,21 @@ func Add() {
|
|||||||
PublicKey: publicKey,
|
PublicKey: publicKey,
|
||||||
PrivateKey: privateKey, // omitted from server config JSON!
|
PrivateKey: privateKey, // omitted from server config JSON!
|
||||||
PresharedKey: GenerateJSONKey(),
|
PresharedKey: GenerateJSONKey(),
|
||||||
IP: IP,
|
|
||||||
Networks: []JSONIPNet{},
|
Networks: []JSONIPNet{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(conf.Network.IPNet.Mask) > 0 {
|
||||||
|
peer.IP = conf.MustAllocateIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.Network6.IPNet.Mask) > 0 {
|
||||||
|
peer.IP6 = conf.MustAllocateIP6()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.IP) == 0 && len(conf.IP6) == 0 {
|
||||||
|
ExitFail("No IPv4 or IPv6 network defined in config")
|
||||||
|
}
|
||||||
|
|
||||||
conf.MustAddPeer(peer)
|
conf.MustAddPeer(peer)
|
||||||
PrintPeerCfg(peer, conf)
|
PrintPeerCfg(peer, conf)
|
||||||
conf.MustSave()
|
conf.MustSave()
|
||||||
@ -83,13 +125,6 @@ func Add() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PrintPeerCfg(peer PeerConfig, conf *DsnetConfig) {
|
func PrintPeerCfg(peer PeerConfig, conf *DsnetConfig) {
|
||||||
allowedIPsStr := make([]string, len(conf.Networks)+1)
|
|
||||||
allowedIPsStr[0] = conf.Network.String()
|
|
||||||
|
|
||||||
for i, net := range conf.Networks {
|
|
||||||
allowedIPsStr[i+1] = net.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
var peerConf string
|
var peerConf string
|
||||||
|
|
||||||
switch os.Getenv("DSNET_OUTPUT") {
|
switch os.Getenv("DSNET_OUTPUT") {
|
||||||
@ -103,15 +138,16 @@ func PrintPeerCfg(peer PeerConfig, conf *DsnetConfig) {
|
|||||||
ExitFail("Unrecognised DSNET_OUTPUT type")
|
ExitFail("Unrecognised DSNET_OUTPUT type")
|
||||||
}
|
}
|
||||||
|
|
||||||
cidrmask, _ := conf.Network.IPNet.Mask.Size()
|
cidrSize, _ := conf.Network.IPNet.Mask.Size()
|
||||||
|
cidrSize6, _ := conf.Network6.IPNet.Mask.Size()
|
||||||
|
|
||||||
t := template.Must(template.New("peerConf").Parse(peerConf))
|
t := template.Must(template.New("peerConf").Parse(peerConf))
|
||||||
err := t.Execute(os.Stdout, map[string]interface{}{
|
err := t.Execute(os.Stdout, map[string]interface{}{
|
||||||
"Peer": peer,
|
"Peer": peer,
|
||||||
"DsnetConfig": conf,
|
"DsnetConfig": conf,
|
||||||
"Keepalive": time.Duration(KEEPALIVE).Seconds(),
|
"Keepalive": time.Duration(KEEPALIVE).Seconds(),
|
||||||
"AllowedIPs": strings.Join(allowedIPsStr, ","),
|
"CidrSize": cidrSize,
|
||||||
"Cidrmask": cidrmask,
|
"CidrSize6": cidrSize6,
|
||||||
})
|
})
|
||||||
check(err)
|
check(err)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package dsnet
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@ -20,7 +21,8 @@ type PeerConfig struct {
|
|||||||
// Description of what the host is and/or does
|
// Description of what the host is and/or does
|
||||||
Description string `validate:"required,gte=1,lte=255"`
|
Description string `validate:"required,gte=1,lte=255"`
|
||||||
// Internal VPN IP address. Added to AllowedIPs in server config as a /32
|
// Internal VPN IP address. Added to AllowedIPs in server config as a /32
|
||||||
IP net.IP `validate:"required`
|
IP net.IP
|
||||||
|
IP6 net.IP
|
||||||
Added time.Time `validate:"required"`
|
Added time.Time `validate:"required"`
|
||||||
// TODO ExternalIP support (Endpoint)
|
// TODO ExternalIP support (Endpoint)
|
||||||
//ExternalIP net.UDPAddr `validate:"required,udp4_addr"`
|
//ExternalIP net.UDPAddr `validate:"required,udp4_addr"`
|
||||||
@ -34,14 +36,17 @@ type PeerConfig struct {
|
|||||||
type DsnetConfig struct {
|
type DsnetConfig struct {
|
||||||
// domain to append to hostnames. Relies on separate DNS server for
|
// domain to append to hostnames. Relies on separate DNS server for
|
||||||
// resolution. Informational only.
|
// resolution. Informational only.
|
||||||
ExternalIP net.IP `validate:"required"`
|
ExternalIP net.IP
|
||||||
|
ExternalIP6 net.IP
|
||||||
ListenPort int `validate:"gte=1024,lte=65535"`
|
ListenPort int `validate:"gte=1024,lte=65535"`
|
||||||
Domain string `validate:"required,gte=1,lte=255"`
|
Domain string `validate:"required,gte=1,lte=255"`
|
||||||
InterfaceName string `validate:"required,gte=1,lte=255"`
|
InterfaceName string `validate:"required,gte=1,lte=255"`
|
||||||
// IP network from which to allocate automatic sequential addresses
|
// IP network from which to allocate automatic sequential addresses
|
||||||
// Network is chosen randomly when not specified
|
// Network is chosen randomly when not specified
|
||||||
Network JSONIPNet `validate:"required"`
|
Network JSONIPNet `validate:"required"`
|
||||||
IP net.IP `validate:"required"`
|
Network6 JSONIPNet `validate:"required"`
|
||||||
|
IP net.IP
|
||||||
|
IP6 net.IP
|
||||||
DNS net.IP
|
DNS net.IP
|
||||||
// extra networks available, will be added to AllowedIPs
|
// extra networks available, will be added to AllowedIPs
|
||||||
Networks []JSONIPNet `validate:"required"`
|
Networks []JSONIPNet `validate:"required"`
|
||||||
@ -69,6 +74,10 @@ func MustLoadDsnetConfig() *DsnetConfig {
|
|||||||
err = validator.New().Struct(conf)
|
err = validator.New().Struct(conf)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
|
if len(conf.ExternalIP) == 0 && len(conf.ExternalIP6) == 0 {
|
||||||
|
ExitFail("Config does not contain ExternalIP or ExternalIP6")
|
||||||
|
}
|
||||||
|
|
||||||
return &conf
|
return &conf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,12 +140,12 @@ func (conf *DsnetConfig) MustRemovePeer(hostname string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (conf DsnetConfig) IPAllocated(IP net.IP) bool {
|
func (conf DsnetConfig) IPAllocated(IP net.IP) bool {
|
||||||
if IP.Equal(conf.IP) {
|
if IP.Equal(conf.IP) || IP.Equal(conf.IP6) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range conf.Peers {
|
for _, peer := range conf.Peers {
|
||||||
if IP.Equal(peer.IP) {
|
if IP.Equal(peer.IP) || IP.Equal(peer.IP6) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,16 +159,21 @@ func (conf DsnetConfig) IPAllocated(IP net.IP) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose a free IP for a new Peer
|
// choose a free IPv4 for a new Peer (sequential allocation)
|
||||||
func (conf DsnetConfig) MustAllocateIP() net.IP {
|
func (conf DsnetConfig) MustAllocateIP() net.IP {
|
||||||
network := conf.Network.IPNet
|
network := conf.Network.IPNet
|
||||||
ones, bits := network.Mask.Size()
|
ones, bits := network.Mask.Size()
|
||||||
zeros := bits - ones
|
zeros := bits - ones
|
||||||
min := 1 // avoids network addr
|
|
||||||
max := (1 << zeros) - 2 // avoids broadcast addr + overflow
|
// avoids network addr
|
||||||
|
min := 1
|
||||||
|
// avoids broadcast addr + overflow
|
||||||
|
max := (1 << zeros) - 2
|
||||||
|
|
||||||
|
IP := make(net.IP, len(network.IP))
|
||||||
|
|
||||||
for i := min; i <= max; i++ {
|
for i := min; i <= max; i++ {
|
||||||
IP := make(net.IP, len(network.IP))
|
// dst, src!
|
||||||
copy(IP, network.IP)
|
copy(IP, network.IP)
|
||||||
|
|
||||||
// OR the host part with the network part
|
// OR the host part with the network part
|
||||||
@ -178,6 +192,37 @@ func (conf DsnetConfig) MustAllocateIP() net.IP {
|
|||||||
return net.IP{}
|
return net.IP{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// choose a free IPv6 for a new Peer (pseudorandom allocation)
|
||||||
|
func (conf DsnetConfig) MustAllocateIP6() net.IP {
|
||||||
|
network := conf.Network6.IPNet
|
||||||
|
ones, bits := network.Mask.Size()
|
||||||
|
zeros := bits - ones
|
||||||
|
|
||||||
|
rbs := make([]byte, zeros)
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
|
IP := make(net.IP, len(network.IP))
|
||||||
|
|
||||||
|
for i := 0; i <= 10000; i++ {
|
||||||
|
rand.Read(rbs)
|
||||||
|
// dst, src! Copy prefix of IP
|
||||||
|
copy(IP, network.IP)
|
||||||
|
|
||||||
|
// OR the host part with the network part
|
||||||
|
for j := ones / 8; j < len(IP); j++ {
|
||||||
|
IP[j] = IP[j] | rbs[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conf.IPAllocated(IP) {
|
||||||
|
return IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitFail("Could not allocate random IPv6 after 10000 tries. This was highly unlikely!")
|
||||||
|
|
||||||
|
return net.IP{}
|
||||||
|
}
|
||||||
|
|
||||||
func (conf DsnetConfig) GetWgPeerConfigs() []wgtypes.PeerConfig {
|
func (conf DsnetConfig) GetWgPeerConfigs() []wgtypes.PeerConfig {
|
||||||
wgPeers := make([]wgtypes.PeerConfig, 0, len(conf.Peers))
|
wgPeers := make([]wgtypes.PeerConfig, 0, len(conf.Peers))
|
||||||
|
|
||||||
@ -187,10 +232,26 @@ func (conf DsnetConfig) GetWgPeerConfigs() []wgtypes.PeerConfig {
|
|||||||
presharedKey := peer.PresharedKey.Key
|
presharedKey := peer.PresharedKey.Key
|
||||||
|
|
||||||
// AllowedIPs = private IP + defined networks
|
// AllowedIPs = private IP + defined networks
|
||||||
allowedIPs := make([]net.IPNet, len(peer.Networks)+1)
|
allowedIPs := make([]net.IPNet, 0, len(peer.Networks)+2)
|
||||||
allowedIPs[0] = net.IPNet{
|
|
||||||
|
if len(peer.IP) > 0 {
|
||||||
|
allowedIPs = append(
|
||||||
|
allowedIPs,
|
||||||
|
net.IPNet{
|
||||||
IP: peer.IP,
|
IP: peer.IP,
|
||||||
Mask: net.IPMask{255, 255, 255, 255},
|
Mask: net.IPMask{255, 255, 255, 255},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(peer.IP6) > 0 {
|
||||||
|
allowedIPs = append(
|
||||||
|
allowedIPs,
|
||||||
|
net.IPNet{
|
||||||
|
IP: peer.IP6,
|
||||||
|
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, net := range peer.Networks {
|
for i, net := range peer.Networks {
|
||||||
|
16
exttypes.go
16
exttypes.go
@ -12,14 +12,30 @@ type JSONIPNet struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n JSONIPNet) MarshalJSON() ([]byte, error) {
|
func (n JSONIPNet) MarshalJSON() ([]byte, error) {
|
||||||
|
if len(n.IPNet.IP) == 0 {
|
||||||
|
return []byte("\"\""), nil
|
||||||
|
} else {
|
||||||
return []byte("\"" + n.IPNet.String() + "\""), nil
|
return []byte("\"" + n.IPNet.String() + "\""), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *JSONIPNet) UnmarshalJSON(b []byte) error {
|
func (n *JSONIPNet) UnmarshalJSON(b []byte) error {
|
||||||
cidr := strings.Trim(string(b), "\"")
|
cidr := strings.Trim(string(b), "\"")
|
||||||
|
|
||||||
|
if cidr == "" {
|
||||||
|
// Leave as empty/uninitialised IPNet. A bit like omitempty behaviour,
|
||||||
|
// but we can leave the field there and blank which is useful if the
|
||||||
|
// user wishes to add the cidr manually.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
IP, IPNet, err := net.ParseCIDR(cidr)
|
IP, IPNet, err := net.ParseCIDR(cidr)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
IPNet.IP = IP
|
IPNet.IP = IP
|
||||||
n.IPNet = *IPNet
|
n.IPNet = *IPNet
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
78
init.go
78
init.go
@ -21,27 +21,34 @@ func Init() {
|
|||||||
conf := DsnetConfig{
|
conf := DsnetConfig{
|
||||||
PrivateKey: GenerateJSONPrivateKey(),
|
PrivateKey: GenerateJSONPrivateKey(),
|
||||||
ListenPort: DEFAULT_LISTEN_PORT,
|
ListenPort: DEFAULT_LISTEN_PORT,
|
||||||
Network: getRandomNetwork(),
|
Network: getPrivateNet(),
|
||||||
|
Network6: getULANet(),
|
||||||
Peers: []PeerConfig{},
|
Peers: []PeerConfig{},
|
||||||
Domain: "dsnet",
|
Domain: "dsnet",
|
||||||
ReportFile: DEFAULT_REPORT_FILE,
|
ReportFile: DEFAULT_REPORT_FILE,
|
||||||
ExternalIP: getExternalIP(),
|
ExternalIP: getExternalIP(),
|
||||||
|
ExternalIP6: getExternalIP6(),
|
||||||
InterfaceName: DEFAULT_INTERFACE_NAME,
|
InterfaceName: DEFAULT_INTERFACE_NAME,
|
||||||
Networks: []JSONIPNet{},
|
Networks: []JSONIPNet{},
|
||||||
}
|
}
|
||||||
|
|
||||||
IP := conf.MustAllocateIP()
|
conf.IP = conf.MustAllocateIP()
|
||||||
conf.IP = IP
|
conf.IP6 = conf.MustAllocateIP6()
|
||||||
|
|
||||||
|
if len(conf.ExternalIP) == 0 && len(conf.ExternalIP6) == 0 {
|
||||||
|
ExitFail("Could not determine any external IP, v4 or v6")
|
||||||
|
}
|
||||||
|
|
||||||
// DNS not set by default
|
// DNS not set by default
|
||||||
//conf.DNS = IP
|
//conf.DNS = IP
|
||||||
|
|
||||||
conf.MustSave()
|
conf.MustSave()
|
||||||
|
|
||||||
fmt.Printf("Config written to %s. Please check/edit.", CONFIG_FILE)
|
fmt.Printf("Config written to %s. Please check/edit.\n", CONFIG_FILE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a random /22 subnet on 10.0.0.0 (1023 hosts) (or /24?)
|
// get a random IPv4 /22 subnet on 10.0.0.0 (1023 hosts) (or /24?)
|
||||||
func getRandomNetwork() JSONIPNet {
|
func getPrivateNet() JSONIPNet {
|
||||||
rbs := make([]byte, 2)
|
rbs := make([]byte, 2)
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
rand.Read(rbs)
|
rand.Read(rbs)
|
||||||
@ -54,20 +61,38 @@ func getRandomNetwork() JSONIPNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO support IPv6
|
func getULANet() JSONIPNet {
|
||||||
|
rbs := make([]byte, 5)
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
rand.Read(rbs)
|
||||||
|
|
||||||
|
// fd00 prefix with 40 bit global id and zero (16 bit) subnet ID
|
||||||
|
return JSONIPNet{
|
||||||
|
IPNet: net.IPNet{
|
||||||
|
net.IP{0xfd, 0, rbs[0], rbs[1], rbs[2], rbs[3], rbs[4], 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO factor getExternalIP + getExternalIP6
|
||||||
func getExternalIP() net.IP {
|
func getExternalIP() net.IP {
|
||||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
var IP net.IP
|
||||||
check(err, "Could not detect internet connection")
|
// arbitrary external IP is used (one that's guaranteed to route outside.
|
||||||
|
// In this case, Google's DNS server. Doesn't actually need to be online.)
|
||||||
|
conn, err := net.Dial("udp", "8.8.8.8:53")
|
||||||
|
if err == nil {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
localAddr := conn.LocalAddr().String()
|
localAddr := conn.LocalAddr().String()
|
||||||
IP := net.ParseIP(strings.Split(localAddr, ":")[0])
|
IP = net.ParseIP(strings.Split(localAddr, ":")[0])
|
||||||
IP = IP.To4()
|
IP = IP.To4()
|
||||||
|
|
||||||
if !(IP[0] == 10 || (IP[0] == 172 && IP[1] >= 16 && IP[1] <= 31) || (IP[0] == 192 && IP[1] == 168)) {
|
if !(IP[0] == 10 || (IP[0] == 172 && IP[1] >= 16 && IP[1] <= 31) || (IP[0] == 192 && IP[1] == 168)) {
|
||||||
// not private, so public
|
// not private, so public
|
||||||
return IP
|
return IP
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// detect private IP and use icanhazip.com instead
|
// detect private IP and use icanhazip.com instead
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
@ -86,3 +111,36 @@ func getExternalIP() net.IP {
|
|||||||
|
|
||||||
return net.IP{}
|
return net.IP{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExternalIP6() net.IP {
|
||||||
|
var IP net.IP
|
||||||
|
conn, err := net.Dial("udp", "2001:4860:4860::8888:53")
|
||||||
|
if err == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
localAddr := conn.LocalAddr().String()
|
||||||
|
IP = net.ParseIP(strings.Split(localAddr, ":")[0])
|
||||||
|
|
||||||
|
// check is not a ULA
|
||||||
|
if IP[0] != 0xfd && IP[0] != 0xfc {
|
||||||
|
return IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("https://ipv6.icanhazip.com/")
|
||||||
|
if err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
check(err)
|
||||||
|
IP = net.ParseIP(strings.TrimSpace(string(body)))
|
||||||
|
return IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.IP{}
|
||||||
|
}
|
||||||
|
2
util.go
2
util.go
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func check(e error, optMsg ...string) {
|
func check(e error, optMsg ...string) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
if (len(optMsg) > 0) {
|
if len(optMsg) > 0 {
|
||||||
ExitFail("%s - %s", e, strings.Join(optMsg, " "))
|
ExitFail("%s - %s", e, strings.Join(optMsg, " "))
|
||||||
}
|
}
|
||||||
ExitFail("%s", e)
|
ExitFail("%s", e)
|
||||||
|
Loading…
Reference in New Issue
Block a user