Browse Source

Merge pull request #6170 from heyitsanthony/default-advertise-ip

use default ip for advertise URL
Anthony Romano 9 years ago
parent
commit
16b2d9ca5e
4 changed files with 211 additions and 8 deletions
  1. 36 7
      embed/config.go
  2. 14 1
      etcdmain/etcd.go
  3. 28 0
      pkg/netutil/routes.go
  4. 133 0
      pkg/netutil/routes_linux.go

+ 36 - 7
embed/config.go

@@ -24,6 +24,7 @@ import (
 	"github.com/coreos/etcd/discovery"
 	"github.com/coreos/etcd/etcdserver"
 	"github.com/coreos/etcd/pkg/cors"
+	"github.com/coreos/etcd/pkg/netutil"
 	"github.com/coreos/etcd/pkg/transport"
 	"github.com/coreos/etcd/pkg/types"
 	"github.com/ghodss/yaml"
@@ -33,13 +34,9 @@ const (
 	ClusterStateFlagNew      = "new"
 	ClusterStateFlagExisting = "existing"
 
-	DefaultName                     = "default"
-	DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
-	DefaultAdvertiseClientURLs      = "http://localhost:2379"
-	DefaultListenPeerURLs           = "http://localhost:2380"
-	DefaultListenClientURLs         = "http://localhost:2379"
-	DefaultMaxSnapshots             = 5
-	DefaultMaxWALs                  = 5
+	DefaultName         = "default"
+	DefaultMaxSnapshots = 5
+	DefaultMaxWALs      = 5
 
 	// maxElectionMs specifies the maximum value of election timeout.
 	// More details are listed in ../Documentation/tuning.md#time-parameters.
@@ -50,8 +47,28 @@ var (
 	ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
 		"Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
 	ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
+
+	DefaultListenPeerURLs           = "http://localhost:2380"
+	DefaultListenClientURLs         = "http://localhost:2379"
+	DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
+	DefaultAdvertiseClientURLs      = "http://localhost:2379"
+
+	defaultHostname   string = "localhost"
+	defaultHostStatus error
 )
 
+func init() {
+	ip, err := netutil.GetDefaultHost()
+	if err != nil {
+		defaultHostStatus = err
+		return
+	}
+	// found default host, advertise on it
+	DefaultInitialAdvertisePeerURLs = "http://" + ip + ":2380"
+	DefaultAdvertiseClientURLs = "http://" + ip + ":2379"
+	defaultHostname = ip
+}
+
 // Config holds the arguments for configuring an etcd server.
 type Config struct {
 	// member
@@ -317,3 +334,15 @@ func (cfg Config) InitialClusterFromName(name string) (ret string) {
 
 func (cfg Config) IsNewCluster() bool { return cfg.ClusterState == ClusterStateFlagNew }
 func (cfg Config) ElectionTicks() int { return int(cfg.ElectionMs / cfg.TickMs) }
+
+// IsDefaultHost returns the default hostname, if used, and the error, if any,
+// from getting the machine's default host.
+func (cfg Config) IsDefaultHost() (string, error) {
+	if len(cfg.APUrls) == 1 && cfg.APUrls[0].String() == DefaultInitialAdvertisePeerURLs {
+		return defaultHostname, defaultHostStatus
+	}
+	if len(cfg.ACUrls) == 1 && cfg.ACUrls[0].String() == DefaultAdvertiseClientURLs {
+		return defaultHostname, defaultHostStatus
+	}
+	return "", defaultHostStatus
+}

+ 14 - 1
etcdmain/etcd.go

@@ -60,6 +60,8 @@ func startEtcdOrProxyV2() {
 	grpc.EnableTracing = false
 
 	cfg := newConfig()
+	defaultInitialCluster := cfg.InitialCluster
+
 	err := cfg.parse(os.Args[1:])
 	if err != nil {
 		plog.Errorf("error verifying flags, %v. See 'etcd --help'.", err)
@@ -83,7 +85,9 @@ func startEtcdOrProxyV2() {
 	plog.Infof("setting maximum number of CPUs to %d, total number of available CPUs is %d", GoMaxProcs, runtime.NumCPU())
 
 	// TODO: check whether fields are set instead of whether fields have default value
-	if cfg.Name != embed.DefaultName && cfg.InitialCluster == cfg.InitialClusterFromName(embed.DefaultName) {
+	defaultHost, defaultHostErr := cfg.IsDefaultHost()
+	defaultHostOverride := defaultHost == "" || defaultHostErr == nil
+	if (defaultHostOverride || cfg.Name != embed.DefaultName) && cfg.InitialCluster == defaultInitialCluster {
 		cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
 	}
 
@@ -184,6 +188,15 @@ func startEtcdOrProxyV2() {
 
 // startEtcd runs StartEtcd in addition to hooks needed for standalone etcd.
 func startEtcd(cfg *embed.Config) (<-chan struct{}, <-chan error, error) {
+	defaultHost, dhErr := cfg.IsDefaultHost()
+	if defaultHost != "" {
+		if dhErr == nil {
+			plog.Infof("advertising using detected default host %q", defaultHost)
+		} else {
+			plog.Noticef("failed to detect default host, advertise falling back to %q (%v)", defaultHost, dhErr)
+		}
+	}
+
 	e, err := embed.StartEtcd(cfg)
 	if err != nil {
 		return nil, nil, err

+ 28 - 0
pkg/netutil/routes.go

@@ -0,0 +1,28 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !linux !386,!amd64
+
+package netutil
+
+import (
+	"fmt"
+	"runtime"
+)
+
+// GetDefaultHost fetches the a resolvable name that corresponds
+// to the machine's default routable interface
+func GetDefaultHost() (string, error) {
+	return "", fmt.Errorf("default host not supported on %s_%s", runtime.GOOS, runtime.GOARCH)
+}

+ 133 - 0
pkg/netutil/routes_linux.go

@@ -0,0 +1,133 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build linux
+// +build 386 amd64
+
+// TODO support native endian but without using "unsafe"
+
+package netutil
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"net"
+	"syscall"
+)
+
+var errNoDefaultRoute = fmt.Errorf("could not find default route")
+
+func GetDefaultHost() (string, error) {
+	rmsg, rerr := getDefaultRoute()
+	if rerr != nil {
+		return "", rerr
+	}
+
+	attrs, aerr := syscall.ParseNetlinkRouteAttr(rmsg)
+	if aerr != nil {
+		return "", aerr
+	}
+
+	oif := uint32(0)
+	for _, attr := range attrs {
+		if attr.Attr.Type == syscall.RTA_PREFSRC {
+			return net.IP(attr.Value).String(), nil
+		}
+		if attr.Attr.Type == syscall.RTA_OIF {
+			oif = binary.LittleEndian.Uint32(attr.Value)
+		}
+	}
+
+	if oif == 0 {
+		return "", errNoDefaultRoute
+	}
+
+	// prefsrc not detected, fall back to getting address from iface
+
+	ifmsg, ierr := getIface(oif)
+	if ierr != nil {
+		return "", ierr
+	}
+
+	attrs, aerr = syscall.ParseNetlinkRouteAttr(ifmsg)
+	if aerr != nil {
+		return "", aerr
+	}
+
+	for _, attr := range attrs {
+		if attr.Attr.Type == syscall.RTA_SRC {
+			return net.IP(attr.Value).String(), nil
+		}
+	}
+
+	return "", errNoDefaultRoute
+}
+
+func getDefaultRoute() (*syscall.NetlinkMessage, error) {
+	dat, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
+	if err != nil {
+		return nil, err
+	}
+
+	msgs, msgErr := syscall.ParseNetlinkMessage(dat)
+	if msgErr != nil {
+		return nil, msgErr
+	}
+
+	rtmsg := syscall.RtMsg{}
+	for _, m := range msgs {
+		if m.Header.Type != syscall.RTM_NEWROUTE {
+			continue
+		}
+		buf := bytes.NewBuffer(m.Data[:syscall.SizeofRtMsg])
+		if rerr := binary.Read(buf, binary.LittleEndian, &rtmsg); rerr != nil {
+			continue
+		}
+		if rtmsg.Dst_len == 0 {
+			// zero-length Dst_len implies default route
+			return &m, nil
+		}
+	}
+
+	return nil, errNoDefaultRoute
+}
+
+func getIface(idx uint32) (*syscall.NetlinkMessage, error) {
+	dat, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
+	if err != nil {
+		return nil, err
+	}
+
+	msgs, msgErr := syscall.ParseNetlinkMessage(dat)
+	if msgErr != nil {
+		return nil, msgErr
+	}
+
+	ifaddrmsg := syscall.IfAddrmsg{}
+	for _, m := range msgs {
+		if m.Header.Type != syscall.RTM_NEWADDR {
+			continue
+		}
+		buf := bytes.NewBuffer(m.Data[:syscall.SizeofIfAddrmsg])
+		if rerr := binary.Read(buf, binary.LittleEndian, &ifaddrmsg); rerr != nil {
+			continue
+		}
+		if ifaddrmsg.Index == idx {
+			return &m, nil
+		}
+	}
+
+	return nil, errNoDefaultRoute
+}