From b5d45322b4d7a56ec29f17aacf606feb5faec2e6 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 5 Sep 2015 17:53:41 +0200 Subject: [PATCH 1/2] 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 29d8dc0424..fe495fcf91 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 7f49783dba..f88c11248f 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 58f15933ef..88cbe9e114 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 76146cc0a1..c22178e902 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 From 99eb629c8f256cdf4958b159d1c501cd755c244d Mon Sep 17 00:00:00 2001 From: James Phillips Date: Sat, 19 Mar 2016 16:14:45 -0700 Subject: [PATCH 2/2] Adds more specific checks for ipv6 addresses. --- consul/util.go | 8 ++++++-- consul/util_test.go | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/consul/util.go b/consul/util.go index f88c11248f..8959ee66a2 100644 --- a/consul/util.go +++ b/consul/util.go @@ -275,6 +275,10 @@ func GetPublicIPv6() (net.IP, error) { return getPublicIPv6(addresses) } +func isUniqueLocalAddress(ip net.IP) bool { + return len(ip) == net.IPv6len && ip[0] == 0xfc && ip[1] == 0x00 +} + func getPublicIPv6(addresses []net.Addr) (net.IP, error) { var candidates []net.IP @@ -293,8 +297,8 @@ func getPublicIPv6(addresses []net.Addr) (net.IP, error) { 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 { + + if ip.IsLinkLocalUnicast() || isUniqueLocalAddress(ip) || ip.IsLoopback() { continue } candidates = append(candidates, ip) diff --git a/consul/util_test.go b/consul/util_test.go index 88cbe9e114..1011ec896a 100644 --- a/consul/util_test.go +++ b/consul/util_test.go @@ -287,6 +287,11 @@ func TestGetPublicIPv6(t *testing.T) { t.Fatalf("failed to parse loopback cidr: %v", err) } + ip3, _, err := net.ParseCIDR("fc00::1/128") + if err != nil { + t.Fatalf("failed to parse ULA 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) @@ -305,6 +310,9 @@ func TestGetPublicIPv6(t *testing.T) { &net.IPAddr{ IP: ip2, }, + &net.IPAddr{ + IP: ip3, + }, &net.IPAddr{ IP: pubIP, }, @@ -319,6 +327,9 @@ func TestGetPublicIPv6(t *testing.T) { &net.IPAddr{ IP: ip2, }, + &net.IPAddr{ + IP: ip3, + }, }, err: errors.New("No public IPv6 address found"), },