From 508bc796a89ab9e523eb811a960aebd5124ba9ff Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 5 Sep 2015 17:53:41 +0200 Subject: [PATCH] Allow [::] as a bind address (binds to first public IPv6 address) --- command/agent/agent.go | 10 ++- consul/util.go | 46 +++++++++++ consul/util_test.go | 82 +++++++++++++++++++ .../source/docs/agent/options.html.markdown | 3 +- 4 files changed, 138 insertions(+), 3 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 29d8dc042..fe495fcf9 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -142,10 +142,16 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { if ip := net.ParseIP(config.AdvertiseAddr); ip == nil { return nil, fmt.Errorf("Failed to parse advertise address: %v", config.AdvertiseAddr) } - } else if config.BindAddr != "0.0.0.0" && config.BindAddr != "" { + } else if config.BindAddr != "0.0.0.0" && config.BindAddr != "" && config.BindAddr != "[::]" { config.AdvertiseAddr = config.BindAddr } else { - ip, err := consul.GetPrivateIP() + var err error + var ip net.IP + if config.BindAddr == "[::]" { + ip, err = consul.GetPublicIPv6() + } else { + ip, err = consul.GetPrivateIP() + } if err != nil { return nil, fmt.Errorf("Failed to get advertise address: %v", err) } diff --git a/consul/util.go b/consul/util.go index 7f49783db..f88c11248 100644 --- a/consul/util.go +++ b/consul/util.go @@ -264,6 +264,52 @@ func getPrivateIP(addresses []net.Addr) (net.IP, error) { } +// GetPublicIPv6 is used to return the first public IP address +// associated with an interface on the machine +func GetPublicIPv6() (net.IP, error) { + addresses, err := net.InterfaceAddrs() + if err != nil { + return nil, fmt.Errorf("Failed to get interface addresses: %v", err) + } + + return getPublicIPv6(addresses) +} + +func getPublicIPv6(addresses []net.Addr) (net.IP, error) { + var candidates []net.IP + + // Find public IPv6 address + for _, rawAddr := range addresses { + var ip net.IP + switch addr := rawAddr.(type) { + case *net.IPAddr: + ip = addr.IP + case *net.IPNet: + ip = addr.IP + default: + continue + } + + if ip.To4() != nil { + continue + } + // do not bind link-local (fe80::/10) / ULA (fc00::/7) / loopback (::1) + if ip[0]|0xf == 0xff || ip[0]|0 == 0 { + continue + } + candidates = append(candidates, ip) + } + numIps := len(candidates) + switch numIps { + case 0: + return nil, fmt.Errorf("No public IPv6 address found") + case 1: + return candidates[0], nil + default: + return nil, fmt.Errorf("Multiple public IPv6 addresses found. Please configure one.") + } +} + // Converts bytes to an integer func bytesToUint64(b []byte) uint64 { return binary.BigEndian.Uint64(b) diff --git a/consul/util_test.go b/consul/util_test.go index 58f15933e..88cbe9e11 100644 --- a/consul/util_test.go +++ b/consul/util_test.go @@ -275,3 +275,85 @@ func TestGenerateUUID(t *testing.T) { } } } + +func TestGetPublicIPv6(t *testing.T) { + ip, _, err := net.ParseCIDR("fe80::1/128") + if err != nil { + t.Fatalf("failed to parse link-local cidr: %v", err) + } + + ip2, _, err := net.ParseCIDR("::1/128") + if err != nil { + t.Fatalf("failed to parse loopback cidr: %v", err) + } + + pubIP, _, err := net.ParseCIDR("2001:0db8:85a3::8a2e:0370:7334/128") + if err != nil { + t.Fatalf("failed to parse public cidr: %v", err) + } + + tests := []struct { + addrs []net.Addr + expected net.IP + err error + }{ + { + addrs: []net.Addr{ + &net.IPAddr{ + IP: ip, + }, + &net.IPAddr{ + IP: ip2, + }, + &net.IPAddr{ + IP: pubIP, + }, + }, + expected: pubIP, + }, + { + addrs: []net.Addr{ + &net.IPAddr{ + IP: ip, + }, + &net.IPAddr{ + IP: ip2, + }, + }, + err: errors.New("No public IPv6 address found"), + }, + { + addrs: []net.Addr{ + &net.IPAddr{ + IP: ip, + }, + &net.IPAddr{ + IP: ip, + }, + &net.IPAddr{ + IP: pubIP, + }, + &net.IPAddr{ + IP: pubIP, + }, + }, + err: errors.New("Multiple public IPv6 addresses found. Please configure one."), + }, + } + + for _, test := range tests { + ip, err := getPublicIPv6(test.addrs) + switch { + case test.err != nil && err != nil: + if err.Error() != test.err.Error() { + t.Fatalf("unexpected error: %v != %v", test.err, err) + } + case (test.err == nil && err != nil) || (test.err != nil && err == nil): + t.Fatalf("unexpected error: %v != %v", test.err, err) + default: + if !test.expected.Equal(ip) { + t.Fatalf("unexpected ip: %v != %v", ip, test.expected) + } + } + } +} diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 76146cc0a..c22178e90 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -93,7 +93,8 @@ The options below are all specified on the command-line. for internal cluster communications. This is an IP address that should be reachable by all other nodes in the cluster. By default, this is "0.0.0.0", meaning Consul will use the first available private - IP address. Consul uses both TCP and UDP and the same port for both. If you + IPv4 address. If you specify "[::]", Consul will use the first available public IPv6 address. + Consul uses both TCP and UDP and the same port for both. If you have any firewalls, be sure to allow both protocols. * `-client` - The address to which