khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 1 | // Copyright 2016 The etcd Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | // +build linux |
| 16 | |
| 17 | package netutil |
| 18 | |
| 19 | import ( |
| 20 | "bytes" |
| 21 | "encoding/binary" |
| 22 | "fmt" |
| 23 | "net" |
| 24 | "sort" |
| 25 | "syscall" |
| 26 | |
| 27 | "go.etcd.io/etcd/pkg/cpuutil" |
| 28 | ) |
| 29 | |
| 30 | var errNoDefaultRoute = fmt.Errorf("could not find default route") |
| 31 | var errNoDefaultHost = fmt.Errorf("could not find default host") |
| 32 | var errNoDefaultInterface = fmt.Errorf("could not find default interface") |
| 33 | |
| 34 | // GetDefaultHost obtains the first IP address of machine from the routing table and returns the IP address as string. |
| 35 | // An IPv4 address is preferred to an IPv6 address for backward compatibility. |
| 36 | func GetDefaultHost() (string, error) { |
| 37 | rmsgs, rerr := getDefaultRoutes() |
| 38 | if rerr != nil { |
| 39 | return "", rerr |
| 40 | } |
| 41 | |
| 42 | // prioritize IPv4 |
| 43 | if rmsg, ok := rmsgs[syscall.AF_INET]; ok { |
| 44 | if host, err := chooseHost(syscall.AF_INET, rmsg); host != "" || err != nil { |
| 45 | return host, err |
| 46 | } |
| 47 | delete(rmsgs, syscall.AF_INET) |
| 48 | } |
| 49 | |
| 50 | // sort so choice is deterministic |
| 51 | var families []int |
| 52 | for family := range rmsgs { |
| 53 | families = append(families, int(family)) |
| 54 | } |
| 55 | sort.Ints(families) |
| 56 | |
| 57 | for _, f := range families { |
| 58 | family := uint8(f) |
| 59 | if host, err := chooseHost(family, rmsgs[family]); host != "" || err != nil { |
| 60 | return host, err |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | return "", errNoDefaultHost |
| 65 | } |
| 66 | |
| 67 | func chooseHost(family uint8, rmsg *syscall.NetlinkMessage) (string, error) { |
| 68 | host, oif, err := parsePREFSRC(rmsg) |
| 69 | if host != "" || err != nil { |
| 70 | return host, err |
| 71 | } |
| 72 | |
| 73 | // prefsrc not detected, fall back to getting address from iface |
| 74 | ifmsg, ierr := getIfaceAddr(oif, family) |
| 75 | if ierr != nil { |
| 76 | return "", ierr |
| 77 | } |
| 78 | |
| 79 | attrs, aerr := syscall.ParseNetlinkRouteAttr(ifmsg) |
| 80 | if aerr != nil { |
| 81 | return "", aerr |
| 82 | } |
| 83 | |
| 84 | for _, attr := range attrs { |
| 85 | // search for RTA_DST because ipv6 doesn't have RTA_SRC |
| 86 | if attr.Attr.Type == syscall.RTA_DST { |
| 87 | return net.IP(attr.Value).String(), nil |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | return "", nil |
| 92 | } |
| 93 | |
| 94 | func getDefaultRoutes() (map[uint8]*syscall.NetlinkMessage, error) { |
| 95 | dat, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) |
| 96 | if err != nil { |
| 97 | return nil, err |
| 98 | } |
| 99 | |
| 100 | msgs, msgErr := syscall.ParseNetlinkMessage(dat) |
| 101 | if msgErr != nil { |
| 102 | return nil, msgErr |
| 103 | } |
| 104 | |
| 105 | routes := make(map[uint8]*syscall.NetlinkMessage) |
| 106 | rtmsg := syscall.RtMsg{} |
| 107 | for _, m := range msgs { |
| 108 | if m.Header.Type != syscall.RTM_NEWROUTE { |
| 109 | continue |
| 110 | } |
| 111 | buf := bytes.NewBuffer(m.Data[:syscall.SizeofRtMsg]) |
| 112 | if rerr := binary.Read(buf, cpuutil.ByteOrder(), &rtmsg); rerr != nil { |
| 113 | continue |
| 114 | } |
| 115 | if rtmsg.Dst_len == 0 && rtmsg.Table == syscall.RT_TABLE_MAIN { |
| 116 | // zero-length Dst_len implies default route |
| 117 | msg := m |
| 118 | routes[rtmsg.Family] = &msg |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | if len(routes) > 0 { |
| 123 | return routes, nil |
| 124 | } |
| 125 | |
| 126 | return nil, errNoDefaultRoute |
| 127 | } |
| 128 | |
| 129 | // Used to get an address of interface. |
| 130 | func getIfaceAddr(idx uint32, family uint8) (*syscall.NetlinkMessage, error) { |
| 131 | dat, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, int(family)) |
| 132 | if err != nil { |
| 133 | return nil, err |
| 134 | } |
| 135 | |
| 136 | msgs, msgErr := syscall.ParseNetlinkMessage(dat) |
| 137 | if msgErr != nil { |
| 138 | return nil, msgErr |
| 139 | } |
| 140 | |
| 141 | ifaddrmsg := syscall.IfAddrmsg{} |
| 142 | for _, m := range msgs { |
| 143 | if m.Header.Type != syscall.RTM_NEWADDR { |
| 144 | continue |
| 145 | } |
| 146 | buf := bytes.NewBuffer(m.Data[:syscall.SizeofIfAddrmsg]) |
| 147 | if rerr := binary.Read(buf, cpuutil.ByteOrder(), &ifaddrmsg); rerr != nil { |
| 148 | continue |
| 149 | } |
| 150 | if ifaddrmsg.Index == idx { |
| 151 | return &m, nil |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | return nil, fmt.Errorf("could not find address for interface index %v", idx) |
| 156 | |
| 157 | } |
| 158 | |
| 159 | // Used to get a name of interface. |
| 160 | func getIfaceLink(idx uint32) (*syscall.NetlinkMessage, error) { |
| 161 | dat, err := syscall.NetlinkRIB(syscall.RTM_GETLINK, syscall.AF_UNSPEC) |
| 162 | if err != nil { |
| 163 | return nil, err |
| 164 | } |
| 165 | |
| 166 | msgs, msgErr := syscall.ParseNetlinkMessage(dat) |
| 167 | if msgErr != nil { |
| 168 | return nil, msgErr |
| 169 | } |
| 170 | |
| 171 | ifinfomsg := syscall.IfInfomsg{} |
| 172 | for _, m := range msgs { |
| 173 | if m.Header.Type != syscall.RTM_NEWLINK { |
| 174 | continue |
| 175 | } |
| 176 | buf := bytes.NewBuffer(m.Data[:syscall.SizeofIfInfomsg]) |
| 177 | if rerr := binary.Read(buf, cpuutil.ByteOrder(), &ifinfomsg); rerr != nil { |
| 178 | continue |
| 179 | } |
| 180 | if ifinfomsg.Index == int32(idx) { |
| 181 | return &m, nil |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | return nil, fmt.Errorf("could not find link for interface index %v", idx) |
| 186 | } |
| 187 | |
| 188 | // GetDefaultInterfaces gets names of interfaces and returns a map[interface]families. |
| 189 | func GetDefaultInterfaces() (map[string]uint8, error) { |
| 190 | interfaces := make(map[string]uint8) |
| 191 | rmsgs, rerr := getDefaultRoutes() |
| 192 | if rerr != nil { |
| 193 | return interfaces, rerr |
| 194 | } |
| 195 | |
| 196 | for family, rmsg := range rmsgs { |
| 197 | _, oif, err := parsePREFSRC(rmsg) |
| 198 | if err != nil { |
| 199 | return interfaces, err |
| 200 | } |
| 201 | |
| 202 | ifmsg, ierr := getIfaceLink(oif) |
| 203 | if ierr != nil { |
| 204 | return interfaces, ierr |
| 205 | } |
| 206 | |
| 207 | attrs, aerr := syscall.ParseNetlinkRouteAttr(ifmsg) |
| 208 | if aerr != nil { |
| 209 | return interfaces, aerr |
| 210 | } |
| 211 | |
| 212 | for _, attr := range attrs { |
| 213 | if attr.Attr.Type == syscall.IFLA_IFNAME { |
| 214 | // key is an interface name |
| 215 | // possible values: 2 - AF_INET, 10 - AF_INET6, 12 - dualstack |
| 216 | interfaces[string(attr.Value[:len(attr.Value)-1])] += family |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | if len(interfaces) > 0 { |
| 221 | return interfaces, nil |
| 222 | } |
| 223 | return interfaces, errNoDefaultInterface |
| 224 | } |
| 225 | |
| 226 | // parsePREFSRC returns preferred source address and output interface index (RTA_OIF). |
| 227 | func parsePREFSRC(m *syscall.NetlinkMessage) (host string, oif uint32, err error) { |
| 228 | var attrs []syscall.NetlinkRouteAttr |
| 229 | attrs, err = syscall.ParseNetlinkRouteAttr(m) |
| 230 | if err != nil { |
| 231 | return "", 0, err |
| 232 | } |
| 233 | |
| 234 | for _, attr := range attrs { |
| 235 | if attr.Attr.Type == syscall.RTA_PREFSRC { |
| 236 | host = net.IP(attr.Value).String() |
| 237 | } |
| 238 | if attr.Attr.Type == syscall.RTA_OIF { |
| 239 | oif = cpuutil.ByteOrder().Uint32(attr.Value) |
| 240 | } |
| 241 | if host != "" && oif != uint32(0) { |
| 242 | break |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | if oif == 0 { |
| 247 | err = errNoDefaultRoute |
| 248 | } |
| 249 | return host, oif, err |
| 250 | } |