From d23392ed8e34fd629a2e15c8a1c3cc7bb60c7ec2 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Fri, 12 Aug 2016 15:34:41 -0700 Subject: [PATCH 1/3] netutil: GetDefaultHost for getting the default IP of the host machine --- pkg/netutil/routes.go | 28 ++++++++ pkg/netutil/routes_linux.go | 133 ++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 pkg/netutil/routes.go create mode 100644 pkg/netutil/routes_linux.go diff --git a/pkg/netutil/routes.go b/pkg/netutil/routes.go new file mode 100644 index 000000000..38642c608 --- /dev/null +++ b/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) +} diff --git a/pkg/netutil/routes_linux.go b/pkg/netutil/routes_linux.go new file mode 100644 index 000000000..39040ab02 --- /dev/null +++ b/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 +} From e8594b60b180eae2158dadf6b9a5d5452a0fe18b Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Fri, 12 Aug 2016 19:55:55 -0700 Subject: [PATCH 2/3] embed: use default route IP for default advertise URL Fixes #2858 --- embed/config.go | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/embed/config.go b/embed/config.go index cb1ff1147..94e95902c 100644 --- a/embed/config.go +++ b/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 +} From 2cc245e8bf42fb0e5210543098f3d5a6552f0506 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Mon, 15 Aug 2016 11:11:37 -0700 Subject: [PATCH 3/3] etcdmain: report default advertise detection / fallback --- etcdmain/etcd.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go index 21e9854a5..2444023a4 100644 --- a/etcdmain/etcd.go +++ b/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