Merge pull request #1851 from hashicorp/f-ipv6-bind

Allow [::] as a bind address (binds to first public IPv6 address)
This commit is contained in:
James Phillips 2016-03-19 16:16:19 -07:00
commit b6cd4318d6
4 changed files with 153 additions and 3 deletions

View File

@ -142,10 +142,16 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) {
if ip := net.ParseIP(config.AdvertiseAddr); ip == nil { if ip := net.ParseIP(config.AdvertiseAddr); ip == nil {
return nil, fmt.Errorf("Failed to parse advertise address: %v", config.AdvertiseAddr) 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 config.AdvertiseAddr = config.BindAddr
} else { } 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 { if err != nil {
return nil, fmt.Errorf("Failed to get advertise address: %v", err) return nil, fmt.Errorf("Failed to get advertise address: %v", err)
} }

View File

@ -264,6 +264,56 @@ 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 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
// 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
}
if ip.IsLinkLocalUnicast() || isUniqueLocalAddress(ip) || ip.IsLoopback() {
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 // Converts bytes to an integer
func bytesToUint64(b []byte) uint64 { func bytesToUint64(b []byte) uint64 {
return binary.BigEndian.Uint64(b) return binary.BigEndian.Uint64(b)

View File

@ -275,3 +275,96 @@ 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)
}
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)
}
tests := []struct {
addrs []net.Addr
expected net.IP
err error
}{
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip2,
},
&net.IPAddr{
IP: ip3,
},
&net.IPAddr{
IP: pubIP,
},
},
expected: pubIP,
},
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip2,
},
&net.IPAddr{
IP: ip3,
},
},
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)
}
}
}
}

View File

@ -93,7 +93,8 @@ The options below are all specified on the command-line.
for internal cluster communications. for internal cluster communications.
This is an IP address that should be reachable by all other nodes in the cluster. 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 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. have any firewalls, be sure to allow both protocols.
* <a name="_client"></a><a href="#_client">`-client`</a> - The address to which * <a name="_client"></a><a href="#_client">`-client`</a> - The address to which