forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroutetable_bsd.go
293 lines (257 loc) · 6.93 KB
/
routetable_bsd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin || freebsd
package routetable
import (
"bufio"
"fmt"
"net"
"net/netip"
"runtime"
"sort"
"strings"
"syscall"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
"tailscale.com/types/logger"
)
type RouteEntryBSD struct {
// GatewayInterface is the name of the interface specified as a gateway
// for this route, if any.
GatewayInterface string
// GatewayIdx is the index of the interface specified as a gateway for
// this route, if any.
GatewayIdx int
// GatewayAddr is the link-layer address of the gateway for this route,
// if any.
GatewayAddr string
// Flags contains a string representation of common flags for this
// route.
Flags []string
// RawFlags contains the raw flags that were returned by the operating
// system for this route.
RawFlags int
}
// Format implements the fmt.Formatter interface.
func (r RouteEntryBSD) Format(f fmt.State, verb rune) {
logger.ArgWriter(func(w *bufio.Writer) {
var pstart bool
pr := func(format string, args ...any) {
if pstart {
fmt.Fprintf(w, ", "+format, args...)
} else {
fmt.Fprintf(w, format, args...)
pstart = true
}
}
w.WriteString("{")
if r.GatewayInterface != "" {
pr("GatewayInterface: %s", r.GatewayInterface)
}
if r.GatewayIdx > 0 {
pr("GatewayIdx: %d", r.GatewayIdx)
}
if r.GatewayAddr != "" {
pr("GatewayAddr: %s", r.GatewayAddr)
}
pr("Flags: %v", r.Flags)
unknownFlags := r.RawFlags
for fv := range flags {
if r.RawFlags&fv == fv {
unknownFlags &= ^fv
}
}
if unknownFlags != 0 {
pr("UnknownFlags: %x ", unknownFlags)
}
w.WriteString("}")
}).Format(f, verb)
}
// ipFromRMAddr returns a netip.Addr converted from one of the
// route.Inet{4,6}Addr types.
func ipFromRMAddr(ifs map[int]interfaces.Interface, addr any) netip.Addr {
switch v := addr.(type) {
case *route.Inet4Addr:
return netip.AddrFrom4(v.IP)
case *route.Inet6Addr:
ip := netip.AddrFrom16(v.IP)
if v.ZoneID != 0 {
if iif, ok := ifs[v.ZoneID]; ok {
ip = ip.WithZone(iif.Name)
} else {
ip = ip.WithZone(fmt.Sprint(v.ZoneID))
}
}
return ip
}
return netip.Addr{}
}
// populateGateway populates gateway fields on a RouteEntry/RouteEntryBSD.
func populateGateway(re *RouteEntry, reSys *RouteEntryBSD, ifs map[int]interfaces.Interface, addr any) {
// If the address type has a valid IP, use that.
if ip := ipFromRMAddr(ifs, addr); ip.IsValid() {
re.Gateway = ip
return
}
switch v := addr.(type) {
case *route.LinkAddr:
reSys.GatewayIdx = v.Index
if iif, ok := ifs[v.Index]; ok {
reSys.GatewayInterface = iif.Name
}
var sb strings.Builder
for i, x := range v.Addr {
if i != 0 {
sb.WriteByte(':')
}
fmt.Fprintf(&sb, "%02x", x)
}
reSys.GatewayAddr = sb.String()
}
}
// populateDestination populates the 'Dst' field on a RouteEntry based on the
// RouteMessage's destination and netmask fields.
func populateDestination(re *RouteEntry, ifs map[int]interfaces.Interface, rm *route.RouteMessage) {
dst := rm.Addrs[unix.RTAX_DST]
if dst == nil {
return
}
ip := ipFromRMAddr(ifs, dst)
if !ip.IsValid() {
return
}
if ip.Is4() {
re.Family = 4
} else {
re.Family = 6
}
re.Dst = RouteDestination{
Prefix: netip.PrefixFrom(ip, 32), // default if nothing more specific
}
// If the RTF_HOST flag is set, then this is a host route and there's
// no netmask in this RouteMessage.
if rm.Flags&unix.RTF_HOST != 0 {
return
}
// As above if there's no netmask in the list of addrs
if len(rm.Addrs) < unix.RTAX_NETMASK || rm.Addrs[unix.RTAX_NETMASK] == nil {
return
}
nm := ipFromRMAddr(ifs, rm.Addrs[unix.RTAX_NETMASK])
if !ip.IsValid() {
return
}
// Count the number of bits in the netmask IP and use that to make our prefix.
ones, _ /* bits */ := net.IPMask(nm.AsSlice()).Size()
// Print this ourselves instead of using netip.Prefix so that we don't
// lose the zone (since netip.Prefix strips that).
//
// NOTE(andrew): this doesn't print the same values as the 'netstat' tool
// for some addresses on macOS, and I have no idea why. Specifically,
// 'netstat -rn' will show something like:
// ff00::/8 ::1 UmCI lo0
//
// But we will get:
// destination=ff00::/40 [...]
//
// The netmask that we get back from FetchRIB has 32 more bits in it
// than netstat prints, but only for multicast routes.
//
// For consistency's sake, we're going to do the same here so that we
// get the same values as netstat returns.
if runtime.GOOS == "darwin" && ip.Is6() && ip.IsMulticast() && ones > 32 {
ones -= 32
}
re.Dst = RouteDestination{
Prefix: netip.PrefixFrom(ip, ones),
Zone: ip.Zone(),
}
}
// routeEntryFromMsg returns a RouteEntry from a single route.Message
// returned by the operating system.
func routeEntryFromMsg(ifsByIdx map[int]interfaces.Interface, msg route.Message) (RouteEntry, bool) {
rm, ok := msg.(*route.RouteMessage)
if !ok {
return RouteEntry{}, false
}
// Ignore things that we don't understand
if rm.Version < 3 || rm.Version > 5 {
return RouteEntry{}, false
}
if rm.Type != rmExpectedType {
return RouteEntry{}, false
}
if len(rm.Addrs) < unix.RTAX_GATEWAY {
return RouteEntry{}, false
}
if rm.Flags&skipFlags != 0 {
return RouteEntry{}, false
}
reSys := RouteEntryBSD{
RawFlags: rm.Flags,
}
for fv, fs := range flags {
if rm.Flags&fv == fv {
reSys.Flags = append(reSys.Flags, fs)
}
}
sort.Strings(reSys.Flags)
re := RouteEntry{}
hasFlag := func(f int) bool { return rm.Flags&f != 0 }
switch {
case hasFlag(unix.RTF_LOCAL):
re.Type = RouteTypeLocal
case hasFlag(unix.RTF_BROADCAST):
re.Type = RouteTypeBroadcast
case hasFlag(unix.RTF_MULTICAST):
re.Type = RouteTypeMulticast
// From the manpage: "host entry (net otherwise)"
case !hasFlag(unix.RTF_HOST):
re.Type = RouteTypeUnicast
default:
re.Type = RouteTypeOther
}
populateDestination(&re, ifsByIdx, rm)
if unix.RTAX_GATEWAY < len(rm.Addrs) {
populateGateway(&re, &reSys, ifsByIdx, rm.Addrs[unix.RTAX_GATEWAY])
}
if outif, ok := ifsByIdx[rm.Index]; ok {
re.Interface = outif.Name
}
re.Sys = reSys
return re, true
}
// Get returns route entries from the system route table, limited to at most
// 'max' results.
func Get(max int) ([]RouteEntry, error) {
// Fetching the list of interfaces can race with fetching our route
// table, but we do it anyway since it's helpful for debugging.
ifs, err := interfaces.GetList()
if err != nil {
return nil, err
}
ifsByIdx := make(map[int]interfaces.Interface)
for _, iif := range ifs {
ifsByIdx[iif.Index] = iif
}
rib, err := route.FetchRIB(syscall.AF_UNSPEC, ribType, 0)
if err != nil {
return nil, err
}
msgs, err := route.ParseRIB(parseType, rib)
if err != nil {
return nil, err
}
var ret []RouteEntry
for _, m := range msgs {
re, ok := routeEntryFromMsg(ifsByIdx, m)
if ok {
ret = append(ret, re)
if len(ret) == max {
break
}
}
}
return ret, nil
}