Merge pull request #22 from naggie/ipv6

IPv6 support
This commit is contained in:
Callan Bryant 2020-10-29 17:00:47 +00:00 committed by GitHub
commit 227ed206a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 281 additions and 60 deletions

View File

@ -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
View File

@ -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)
} }

View File

@ -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 {

View File

@ -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
View File

@ -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{}
}

View File

@ -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)