Skip to content

Commit 04c2c5b

Browse files
committed
net/interfaces: define DefaultRouteInterface and State.DefaultRouteInterface
It was pretty ill-defined before and mostly for logging. But I wanted to start depending on it, so define what it is and make Windows match the other operating systems, without losing the log output we had before. (and add tests for that) Change-Id: I0fbbba1cfc67a265d09dd6cb738b73f0f6005247 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 96cab21 commit 04c2c5b

File tree

6 files changed

+132
-24
lines changed

6 files changed

+132
-24
lines changed

net/interfaces/interfaces.go

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ func sortIPs(s []netaddr.IP) {
172172
type Interface struct {
173173
*net.Interface
174174
AltAddrs []net.Addr // if non-nil, returned by Addrs
175+
Desc string // extra description (used on Windows)
175176
}
176177

177178
func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) }
@@ -278,13 +279,16 @@ type State struct {
278279
// instead of Wifi. This field is not populated by GetState.
279280
IsExpensive bool
280281

281-
// DefaultRouteInterface is the interface name for the machine's default route.
282+
// DefaultRouteInterface is the interface name for the
283+
// machine's default route.
284+
//
282285
// It is not yet populated on all OSes.
283-
// Its exact value should not be assumed to be a map key for
284-
// the Interface maps above; it's only used for debugging.
286+
//
287+
// When non-empty, its value is the map key into Interface and
288+
// InterfaceIPs.
285289
DefaultRouteInterface string
286290

287-
// HTTPProxy is the HTTP proxy to use.
291+
// HTTPProxy is the HTTP proxy to use, if any.
288292
HTTPProxy string
289293

290294
// PAC is the URL to the Proxy Autoconfig URL, if applicable.
@@ -293,7 +297,13 @@ type State struct {
293297

294298
func (s *State) String() string {
295299
var sb strings.Builder
296-
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface)
300+
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ", s.DefaultRouteInterface)
301+
if s.DefaultRouteInterface != "" {
302+
if iface, ok := s.Interface[s.DefaultRouteInterface]; ok && iface.Desc != "" {
303+
fmt.Fprintf(&sb, "(%s) ", iface.Desc)
304+
}
305+
}
306+
sb.WriteString("ifs={")
297307
ifs := make([]string, 0, len(s.Interface))
298308
for k := range s.Interface {
299309
if anyInterestingIP(s.InterfaceIPs[k]) {
@@ -507,7 +517,16 @@ func GetState() (*State, error) {
507517
return nil, err
508518
}
509519

510-
s.DefaultRouteInterface, _ = DefaultRouteInterface()
520+
dr, _ := DefaultRoute()
521+
s.DefaultRouteInterface = dr.InterfaceName
522+
523+
// Populate description (for Windows, primarily) if present.
524+
if desc := dr.InterfaceDesc; desc != "" {
525+
if iface, ok := s.Interface[dr.InterfaceName]; ok {
526+
iface.Desc = desc
527+
s.Interface[dr.InterfaceName] = iface
528+
}
529+
}
511530

512531
if s.AnyInterfaceUp() {
513532
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
@@ -667,3 +686,36 @@ func netInterfaces() ([]Interface, error) {
667686
}
668687
return ret, nil
669688
}
689+
690+
// DefaultRouteDetails are the
691+
type DefaultRouteDetails struct {
692+
// InterfaceName is the interface name. It must always be populated.
693+
// It's like "eth0" (Linux), "Ethernet 2" (Windows), "en0" (macOS).
694+
InterfaceName string
695+
696+
// InterfaceDesc is populated on Windows at least. It's a
697+
// longer description, like "Red Hat VirtIO Ethernet Adapter".
698+
InterfaceDesc string
699+
700+
// InterfaceIndex is like net.Interface.Index.
701+
// Zero means not populated.
702+
InterfaceIndex int
703+
704+
// TODO(bradfitz): break this out into v4-vs-v6 once that need arises.
705+
}
706+
707+
// DefaultRouteInterface is like DefaultRoute but only returns the
708+
// interface name.
709+
func DefaultRouteInterface() (string, error) {
710+
dr, err := DefaultRoute()
711+
if err != nil {
712+
return "", err
713+
}
714+
return dr.InterfaceName, nil
715+
}
716+
717+
// DefaultRoute returns details of the network interface that owns
718+
// the default route, not including any tailscale interfaces.
719+
func DefaultRoute() (DefaultRouteDetails, error) {
720+
return defaultRoute()
721+
}

net/interfaces/interfaces_darwin.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import (
1616
"inet.af/netaddr"
1717
)
1818

19-
func DefaultRouteInterface() (string, error) {
19+
func defaultRoute() (d DefaultRouteDetails, err error) {
2020
idx, err := DefaultRouteInterfaceIndex()
2121
if err != nil {
22-
return "", err
22+
return d, err
2323
}
2424
iface, err := net.InterfaceByIndex(idx)
2525
if err != nil {
26-
return "", err
26+
return d, err
2727
}
28-
return iface.Name, nil
28+
d.InterfaceName = iface.Name
29+
d.InterfaceIndex = idx
30+
return d, nil
2931
}
3032

3133
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP2.

net/interfaces/interfaces_defaultrouteif_todo.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ import "errors"
1111

1212
var errTODO = errors.New("TODO")
1313

14-
func DefaultRouteInterface() (string, error) {
15-
return "TODO", errTODO
14+
func defaultRoute() (DefaultRouteDetails, error) {
15+
return DefaultRouteDetails{}, errTODO
1616
}

net/interfaces/interfaces_linux.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,18 @@ func likelyHomeRouterIPAndroid() (ret netaddr.IP, ok bool) {
122122
return ret, !ret.IsZero()
123123
}
124124

125-
// DefaultRouteInterface returns the name of the network interface that owns
126-
// the default route, not including any tailscale interfaces.
127-
func DefaultRouteInterface() (string, error) {
125+
func defaultRoute() (d DefaultRouteDetails, err error) {
128126
v, err := defaultRouteInterfaceProcNet()
129127
if err == nil {
130-
return v, nil
128+
d.InterfaceName = v
129+
return d, nil
131130
}
132131
if runtime.GOOS == "android" {
133-
return defaultRouteInterfaceAndroidIPRoute()
132+
v, err = defaultRouteInterfaceAndroidIPRoute()
133+
d.InterfaceName = v
134+
return d, err
134135
}
135-
return v, err
136+
return d, err
136137
}
137138

138139
var zeroRouteBytes = []byte("00000000")

net/interfaces/interfaces_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,55 @@ func TestStateEqualFilteredIPFilter(t *testing.T) {
104104
t.Errorf("%+v == %+v when restricting to interesting interfaces and IPs", s1, s2)
105105
}
106106
}
107+
108+
func TestStateString(t *testing.T) {
109+
tests := []struct {
110+
name string
111+
s *State
112+
want string
113+
}{
114+
{
115+
name: "typical_linux",
116+
s: &State{
117+
DefaultRouteInterface: "eth0",
118+
Interface: map[string]Interface{
119+
"eth0": {
120+
Interface: &net.Interface{
121+
Flags: net.FlagUp,
122+
},
123+
},
124+
"wlan0": {
125+
Interface: &net.Interface{},
126+
},
127+
},
128+
InterfaceIPs: map[string][]netaddr.IPPrefix{
129+
"eth0": []netaddr.IPPrefix{
130+
netaddr.MustParseIPPrefix("10.0.0.2/8"),
131+
},
132+
},
133+
HaveV4: true,
134+
},
135+
want: `interfaces.State{defaultRoute=eth0 ifs={eth0:[10.0.0.2/8]} v4=true v6=false}`,
136+
},
137+
{
138+
name: "default_desc",
139+
s: &State{
140+
DefaultRouteInterface: "foo",
141+
Interface: map[string]Interface{
142+
"foo": {
143+
Desc: "a foo thing",
144+
},
145+
},
146+
},
147+
want: `interfaces.State{defaultRoute=foo (a foo thing) ifs={} v4=false v6=false}`,
148+
},
149+
}
150+
for _, tt := range tests {
151+
t.Run(tt.name, func(t *testing.T) {
152+
got := tt.s.String()
153+
if got != tt.want {
154+
t.Errorf("wrong\n got: %s\nwant: %s\n", got, tt.want)
155+
}
156+
})
157+
}
158+
}

net/interfaces/interfaces_windows.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package interfaces
66

77
import (
8-
"fmt"
98
"log"
109
"net"
1110
"net/url"
@@ -217,18 +216,20 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
217216
return bestIface, nil
218217
}
219218

220-
func DefaultRouteInterface() (string, error) {
219+
func defaultRoute() (d DefaultRouteDetails, err error) {
221220
// We always return the IPv4 default route.
222221
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
223222
// in which case we might send traffic to the wrong interface.
224223
iface, err := GetWindowsDefault(windows.AF_INET)
225224
if err != nil {
226-
return "", err
225+
return d, err
227226
}
228-
if iface == nil {
229-
return "(none)", nil
227+
if iface != nil {
228+
d.InterfaceName = iface.FriendlyName()
229+
d.InterfaceDesc = iface.Description()
230+
d.InterfaceIndex = int(iface.IfIndex)
230231
}
231-
return fmt.Sprintf("%s (%s)", iface.FriendlyName(), iface.Description()), nil
232+
return d, nil
232233
}
233234

234235
var (

0 commit comments

Comments
 (0)