1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package net
6
7import (
8	"errors"
9	"internal/itoa"
10	"sync"
11	"time"
12	_ "unsafe"
13)
14
15// BUG(mikio): On JS, methods and functions related to
16// Interface are not implemented.
17
18// BUG(mikio): On AIX, DragonFly BSD, NetBSD, OpenBSD, Plan 9 and
19// Solaris, the MulticastAddrs method of Interface is not implemented.
20
21// errNoSuchInterface should be an internal detail,
22// but widely used packages access it using linkname.
23// Notable members of the hall of shame include:
24//   - github.com/sagernet/sing
25//
26// Do not remove or change the type signature.
27// See go.dev/issue/67401.
28//
29//go:linkname errNoSuchInterface
30
31var (
32	errInvalidInterface         = errors.New("invalid network interface")
33	errInvalidInterfaceIndex    = errors.New("invalid network interface index")
34	errInvalidInterfaceName     = errors.New("invalid network interface name")
35	errNoSuchInterface          = errors.New("no such network interface")
36	errNoSuchMulticastInterface = errors.New("no such multicast network interface")
37)
38
39// Interface represents a mapping between network interface name
40// and index. It also represents network interface facility
41// information.
42type Interface struct {
43	Index        int          // positive integer that starts at one, zero is never used
44	MTU          int          // maximum transmission unit
45	Name         string       // e.g., "en0", "lo0", "eth0.100"
46	HardwareAddr HardwareAddr // IEEE MAC-48, EUI-48 and EUI-64 form
47	Flags        Flags        // e.g., FlagUp, FlagLoopback, FlagMulticast
48}
49
50type Flags uint
51
52const (
53	FlagUp           Flags = 1 << iota // interface is administratively up
54	FlagBroadcast                      // interface supports broadcast access capability
55	FlagLoopback                       // interface is a loopback interface
56	FlagPointToPoint                   // interface belongs to a point-to-point link
57	FlagMulticast                      // interface supports multicast access capability
58	FlagRunning                        // interface is in running state
59)
60
61var flagNames = []string{
62	"up",
63	"broadcast",
64	"loopback",
65	"pointtopoint",
66	"multicast",
67	"running",
68}
69
70func (f Flags) String() string {
71	s := ""
72	for i, name := range flagNames {
73		if f&(1<<uint(i)) != 0 {
74			if s != "" {
75				s += "|"
76			}
77			s += name
78		}
79	}
80	if s == "" {
81		s = "0"
82	}
83	return s
84}
85
86// Addrs returns a list of unicast interface addresses for a specific
87// interface.
88func (ifi *Interface) Addrs() ([]Addr, error) {
89	if ifi == nil {
90		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface}
91	}
92	ifat, err := interfaceAddrTable(ifi)
93	if err != nil {
94		err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
95	}
96	return ifat, err
97}
98
99// MulticastAddrs returns a list of multicast, joined group addresses
100// for a specific interface.
101func (ifi *Interface) MulticastAddrs() ([]Addr, error) {
102	if ifi == nil {
103		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface}
104	}
105	ifat, err := interfaceMulticastAddrTable(ifi)
106	if err != nil {
107		err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
108	}
109	return ifat, err
110}
111
112// Interfaces returns a list of the system's network interfaces.
113func Interfaces() ([]Interface, error) {
114	ift, err := interfaceTable(0)
115	if err != nil {
116		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
117	}
118	if len(ift) != 0 {
119		zoneCache.update(ift, false)
120	}
121	return ift, nil
122}
123
124// InterfaceAddrs returns a list of the system's unicast interface
125// addresses.
126//
127// The returned list does not identify the associated interface; use
128// Interfaces and [Interface.Addrs] for more detail.
129func InterfaceAddrs() ([]Addr, error) {
130	ifat, err := interfaceAddrTable(nil)
131	if err != nil {
132		err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
133	}
134	return ifat, err
135}
136
137// InterfaceByIndex returns the interface specified by index.
138//
139// On Solaris, it returns one of the logical network interfaces
140// sharing the logical data link; for more precision use
141// [InterfaceByName].
142func InterfaceByIndex(index int) (*Interface, error) {
143	if index <= 0 {
144		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceIndex}
145	}
146	ift, err := interfaceTable(index)
147	if err != nil {
148		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
149	}
150	ifi, err := interfaceByIndex(ift, index)
151	if err != nil {
152		err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
153	}
154	return ifi, err
155}
156
157func interfaceByIndex(ift []Interface, index int) (*Interface, error) {
158	for _, ifi := range ift {
159		if index == ifi.Index {
160			return &ifi, nil
161		}
162	}
163	return nil, errNoSuchInterface
164}
165
166// InterfaceByName returns the interface specified by name.
167func InterfaceByName(name string) (*Interface, error) {
168	if name == "" {
169		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceName}
170	}
171	ift, err := interfaceTable(0)
172	if err != nil {
173		return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
174	}
175	if len(ift) != 0 {
176		zoneCache.update(ift, false)
177	}
178	for _, ifi := range ift {
179		if name == ifi.Name {
180			return &ifi, nil
181		}
182	}
183	return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errNoSuchInterface}
184}
185
186// An ipv6ZoneCache represents a cache holding partial network
187// interface information. It is used for reducing the cost of IPv6
188// addressing scope zone resolution.
189//
190// Multiple names sharing the index are managed by first-come
191// first-served basis for consistency.
192type ipv6ZoneCache struct {
193	sync.RWMutex                // guard the following
194	lastFetched  time.Time      // last time routing information was fetched
195	toIndex      map[string]int // interface name to its index
196	toName       map[int]string // interface index to its name
197}
198
199var zoneCache = ipv6ZoneCache{
200	toIndex: make(map[string]int),
201	toName:  make(map[int]string),
202}
203
204// update refreshes the network interface information if the cache was last
205// updated more than 1 minute ago, or if force is set. It reports whether the
206// cache was updated.
207func (zc *ipv6ZoneCache) update(ift []Interface, force bool) (updated bool) {
208	zc.Lock()
209	defer zc.Unlock()
210	now := time.Now()
211	if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
212		return false
213	}
214	zc.lastFetched = now
215	if len(ift) == 0 {
216		var err error
217		if ift, err = interfaceTable(0); err != nil {
218			return false
219		}
220	}
221	zc.toIndex = make(map[string]int, len(ift))
222	zc.toName = make(map[int]string, len(ift))
223	for _, ifi := range ift {
224		zc.toIndex[ifi.Name] = ifi.Index
225		if _, ok := zc.toName[ifi.Index]; !ok {
226			zc.toName[ifi.Index] = ifi.Name
227		}
228	}
229	return true
230}
231
232func (zc *ipv6ZoneCache) name(index int) string {
233	if index == 0 {
234		return ""
235	}
236	updated := zoneCache.update(nil, false)
237	zoneCache.RLock()
238	name, ok := zoneCache.toName[index]
239	zoneCache.RUnlock()
240	if !ok && !updated {
241		zoneCache.update(nil, true)
242		zoneCache.RLock()
243		name, ok = zoneCache.toName[index]
244		zoneCache.RUnlock()
245	}
246	if !ok { // last resort
247		name = itoa.Uitoa(uint(index))
248	}
249	return name
250}
251
252func (zc *ipv6ZoneCache) index(name string) int {
253	if name == "" {
254		return 0
255	}
256	updated := zoneCache.update(nil, false)
257	zoneCache.RLock()
258	index, ok := zoneCache.toIndex[name]
259	zoneCache.RUnlock()
260	if !ok && !updated {
261		zoneCache.update(nil, true)
262		zoneCache.RLock()
263		index, ok = zoneCache.toIndex[name]
264		zoneCache.RUnlock()
265	}
266	if !ok { // last resort
267		index, _, _ = dtoi(name)
268	}
269	return index
270}
271