blob: 5118d3dacd2524cc18bac964c65b451b825eefd1 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// 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
17package netutil
18
19import (
20 "bytes"
21 "encoding/binary"
22 "fmt"
23 "net"
24 "sort"
25 "syscall"
26
27 "go.etcd.io/etcd/pkg/cpuutil"
28)
29
30var errNoDefaultRoute = fmt.Errorf("could not find default route")
31var errNoDefaultHost = fmt.Errorf("could not find default host")
32var 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.
36func 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
67func 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
94func 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.
130func 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.
160func 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.
189func 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).
227func 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}