Skip to content

Commit

Permalink
Merge pull request #1851 from hashicorp/f-ipv6-bind
Browse files Browse the repository at this point in the history
Allow [::] as a bind address (binds to first public IPv6 address)
  • Loading branch information
slackpad committed Mar 19, 2016
2 parents 7510f00 + 99eb629 commit b6cd431
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 3 deletions.
10 changes: 8 additions & 2 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
50 changes: 50 additions & 0 deletions consul/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
func bytesToUint64(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
Expand Down
93 changes: 93 additions & 0 deletions consul/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
3 changes: 2 additions & 1 deletion website/source/docs/agent/options.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.

* <a name="_client"></a><a href="#_client">`-client`</a> - The address to which
Expand Down

0 comments on commit b6cd431

Please sign in to comment.