summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/stack/neighbor_entry.go
blob: 65fbd0ac30300030513fd9d8d2bbac19178be274 (plain)
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package stack

import (
	"fmt"
	"sync"
	"time"

	"gvisor.dev/gvisor/pkg/sleep"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/header"
)

const (
	// immediateDuration is a duration of zero for scheduling work that needs to
	// be done immediately but asynchronously to avoid deadlock.
	immediateDuration time.Duration = 0
)

// NeighborEntry describes a neighboring device in the local network.
type NeighborEntry struct {
	Addr           tcpip.Address
	LinkAddr       tcpip.LinkAddress
	State          NeighborState
	UpdatedAtNanos int64
}

// NeighborState defines the state of a NeighborEntry within the Neighbor
// Unreachability Detection state machine, as per RFC 4861 section 7.3.2.
type NeighborState uint8

const (
	// Unknown means reachability has not been verified yet. This is the initial
	// state of entries that have been created automatically by the Neighbor
	// Unreachability Detection state machine.
	Unknown NeighborState = iota
	// Incomplete means that there is an outstanding request to resolve the
	// address.
	Incomplete
	// Reachable means the path to the neighbor is functioning properly for both
	// receive and transmit paths.
	Reachable
	// Stale means reachability to the neighbor is unknown, but packets are still
	// able to be transmitted to the possibly stale link address.
	Stale
	// Delay means reachability to the neighbor is unknown and pending
	// confirmation from an upper-level protocol like TCP, but packets are still
	// able to be transmitted to the possibly stale link address.
	Delay
	// Probe means a reachability confirmation is actively being sought by
	// periodically retransmitting reachability probes until a reachability
	// confirmation is received, or until the max amount of probes has been sent.
	Probe
	// Static describes entries that have been explicitly added by the user. They
	// do not expire and are not deleted until explicitly removed.
	Static
	// Failed means traffic should not be sent to this neighbor since attempts of
	// reachability have returned inconclusive.
	Failed
)

// neighborEntry implements a neighbor entry's individual node behavior, as per
// RFC 4861 section 7.3.3. Neighbor Unreachability Detection operates in
// parallel with the sending of packets to a neighbor, necessitating the
// entry's lock to be acquired for all operations.
type neighborEntry struct {
	neighborEntryEntry

	nic *NIC

	// linkRes provides the functionality to send reachability probes, used in
	// Neighbor Unreachability Detection.
	linkRes LinkAddressResolver

	// nudState points to the Neighbor Unreachability Detection configuration.
	nudState *NUDState

	// mu protects the fields below.
	mu sync.RWMutex

	neigh NeighborEntry

	// wakers is a set of waiters for address resolution result. Anytime state
	// transitions out of incomplete these waiters are notified. It is nil iff
	// address resolution is ongoing and no clients are waiting for the result.
	wakers map[*sleep.Waker]struct{}

	// done is used to allow callers to wait on address resolution. It is nil
	// iff nudState is not Reachable and address resolution is not yet in
	// progress.
	done chan struct{}

	isRouter bool
	job      *tcpip.Job
}

// newNeighborEntry creates a neighbor cache entry starting at the default
// state, Unknown. Transition out of Unknown by calling either
// `handlePacketQueuedLocked` or `handleProbeLocked` on the newly created
// neighborEntry.
func newNeighborEntry(nic *NIC, remoteAddr tcpip.Address, nudState *NUDState, linkRes LinkAddressResolver) *neighborEntry {
	return &neighborEntry{
		nic:      nic,
		linkRes:  linkRes,
		nudState: nudState,
		neigh: NeighborEntry{
			Addr:  remoteAddr,
			State: Unknown,
		},
	}
}

// newStaticNeighborEntry creates a neighbor cache entry starting at the
// Static state. The entry can only transition out of Static by directly
// calling `setStateLocked`.
func newStaticNeighborEntry(nic *NIC, addr tcpip.Address, linkAddr tcpip.LinkAddress, state *NUDState) *neighborEntry {
	entry := NeighborEntry{
		Addr:           addr,
		LinkAddr:       linkAddr,
		State:          Static,
		UpdatedAtNanos: nic.stack.clock.NowNanoseconds(),
	}
	if nic.stack.nudDisp != nil {
		nic.stack.nudDisp.OnNeighborAdded(nic.id, entry)
	}
	return &neighborEntry{
		nic:      nic,
		nudState: state,
		neigh:    entry,
	}
}

// addWaker adds w to the list of wakers waiting for address resolution.
// Assumes the entry has already been appropriately locked.
func (e *neighborEntry) addWakerLocked(w *sleep.Waker) {
	if w == nil {
		return
	}
	if e.wakers == nil {
		e.wakers = make(map[*sleep.Waker]struct{})
	}
	e.wakers[w] = struct{}{}
}

// notifyWakersLocked notifies those waiting for address resolution, whether it
// succeeded or failed. Assumes the entry has already been appropriately locked.
func (e *neighborEntry) notifyWakersLocked() {
	for w := range e.wakers {
		w.Assert()
	}
	e.wakers = nil
	if ch := e.done; ch != nil {
		close(ch)
		e.done = nil
	}
}

// dispatchAddEventLocked signals to stack's NUD Dispatcher that the entry has
// been added.
func (e *neighborEntry) dispatchAddEventLocked() {
	if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
		nudDisp.OnNeighborAdded(e.nic.id, e.neigh)
	}
}

// dispatchChangeEventLocked signals to stack's NUD Dispatcher that the entry
// has changed state or link-layer address.
func (e *neighborEntry) dispatchChangeEventLocked() {
	if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
		nudDisp.OnNeighborChanged(e.nic.id, e.neigh)
	}
}

// dispatchRemoveEventLocked signals to stack's NUD Dispatcher that the entry
// has been removed.
func (e *neighborEntry) dispatchRemoveEventLocked() {
	if nudDisp := e.nic.stack.nudDisp; nudDisp != nil {
		nudDisp.OnNeighborRemoved(e.nic.id, e.neigh)
	}
}

// setStateLocked transitions the entry to the specified state immediately.
//
// Follows the logic defined in RFC 4861 section 7.3.3.
//
// e.mu MUST be locked.
func (e *neighborEntry) setStateLocked(next NeighborState) {
	// Cancel the previously scheduled action, if there is one. Entries in
	// Unknown, Stale, or Static state do not have scheduled actions.
	if timer := e.job; timer != nil {
		timer.Cancel()
	}

	prev := e.neigh.State
	e.neigh.State = next
	e.neigh.UpdatedAtNanos = e.nic.stack.clock.NowNanoseconds()
	config := e.nudState.Config()

	switch next {
	case Incomplete:
		panic(fmt.Sprintf("should never transition to Incomplete with setStateLocked; neigh = %#v, prev state = %s", e.neigh, prev))

	case Reachable:
		e.job = e.nic.stack.newJob(&e.mu, func() {
			e.setStateLocked(Stale)
			e.dispatchChangeEventLocked()
		})
		e.job.Schedule(e.nudState.ReachableTime())

	case Delay:
		e.job = e.nic.stack.newJob(&e.mu, func() {
			e.setStateLocked(Probe)
			e.dispatchChangeEventLocked()
		})
		e.job.Schedule(config.DelayFirstProbeTime)

	case Probe:
		var retryCounter uint32
		var sendUnicastProbe func()

		sendUnicastProbe = func() {
			if retryCounter == config.MaxUnicastProbes {
				e.dispatchRemoveEventLocked()
				e.setStateLocked(Failed)
				return
			}

			if err := e.linkRes.LinkAddressRequest(e.neigh.Addr, "" /* localAddr */, e.neigh.LinkAddr, e.nic); err != nil {
				e.dispatchRemoveEventLocked()
				e.setStateLocked(Failed)
				return
			}

			retryCounter++
			e.job = e.nic.stack.newJob(&e.mu, sendUnicastProbe)
			e.job.Schedule(config.RetransmitTimer)
		}

		// Send a probe in another gorountine to free this thread of execution
		// for finishing the state transition. This is necessary to avoid
		// deadlock where sending and processing probes are done synchronously,
		// such as loopback and integration tests.
		e.job = e.nic.stack.newJob(&e.mu, sendUnicastProbe)
		e.job.Schedule(immediateDuration)

	case Failed:
		e.notifyWakersLocked()
		e.job = e.nic.stack.newJob(&e.mu, func() {
			e.nic.neigh.removeEntryLocked(e)
		})
		e.job.Schedule(config.UnreachableTime)

	case Unknown, Stale, Static:
		// Do nothing

	default:
		panic(fmt.Sprintf("Invalid state transition from %q to %q", prev, next))
	}
}

// handlePacketQueuedLocked advances the state machine according to a packet
// being queued for outgoing transmission.
//
// Follows the logic defined in RFC 4861 section 7.3.3.
func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
	switch e.neigh.State {
	case Unknown:
		e.neigh.State = Incomplete
		e.neigh.UpdatedAtNanos = e.nic.stack.clock.NowNanoseconds()

		e.dispatchAddEventLocked()

		config := e.nudState.Config()

		var retryCounter uint32
		var sendMulticastProbe func()

		sendMulticastProbe = func() {
			if retryCounter == config.MaxMulticastProbes {
				// "If no Neighbor Advertisement is received after
				// MAX_MULTICAST_SOLICIT solicitations, address resolution has failed.
				// The sender MUST return ICMP destination unreachable indications with
				// code 3 (Address Unreachable) for each packet queued awaiting address
				// resolution." - RFC 4861 section 7.2.2
				//
				// There is no need to send an ICMP destination unreachable indication
				// since the failure to resolve the address is expected to only occur
				// on this node. Thus, redirecting traffic is currently not supported.
				//
				// "If the error occurs on a node other than the node originating the
				// packet, an ICMP error message is generated. If the error occurs on
				// the originating node, an implementation is not required to actually
				// create and send an ICMP error packet to the source, as long as the
				// upper-layer sender is notified through an appropriate mechanism
				// (e.g. return value from a procedure call). Note, however, that an
				// implementation may find it convenient in some cases to return errors
				// to the sender by taking the offending packet, generating an ICMP
				// error message, and then delivering it (locally) through the generic
				// error-handling routines.' - RFC 4861 section 2.1
				e.dispatchRemoveEventLocked()
				e.setStateLocked(Failed)
				return
			}

			// As per RFC 4861 section 7.2.2:
			//
			//  If the source address of the packet prompting the solicitation is the
			//  same as one of the addresses assigned to the outgoing interface, that
			//  address SHOULD be placed in the IP Source Address of the outgoing
			//  solicitation.
			//
			if err := e.linkRes.LinkAddressRequest(e.neigh.Addr, localAddr, "", e.nic); err != nil {
				// There is no need to log the error here; the NUD implementation may
				// assume a working link. A valid link should be the responsibility of
				// the NIC/stack.LinkEndpoint.
				e.dispatchRemoveEventLocked()
				e.setStateLocked(Failed)
				return
			}

			retryCounter++
			e.job = e.nic.stack.newJob(&e.mu, sendMulticastProbe)
			e.job.Schedule(config.RetransmitTimer)
		}

		// Send a probe in another gorountine to free this thread of execution
		// for finishing the state transition. This is necessary to avoid
		// deadlock where sending and processing probes are done synchronously,
		// such as loopback and integration tests.
		e.job = e.nic.stack.newJob(&e.mu, sendMulticastProbe)
		e.job.Schedule(immediateDuration)

	case Stale:
		e.setStateLocked(Delay)
		e.dispatchChangeEventLocked()

	case Incomplete, Reachable, Delay, Probe, Static:
		// Do nothing
	case Failed:
		e.nic.stats.Neighbor.FailedEntryLookups.Increment()
	default:
		panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
	}
}

// handleProbeLocked processes an incoming neighbor probe (e.g. ARP request or
// Neighbor Solicitation for ARP or NDP, respectively).
//
// Follows the logic defined in RFC 4861 section 7.2.3.
func (e *neighborEntry) handleProbeLocked(remoteLinkAddr tcpip.LinkAddress) {
	// Probes MUST be silently discarded if the target address is tentative, does
	// not exist, or not bound to the NIC as per RFC 4861 section 7.2.3. These
	// checks MUST be done by the NetworkEndpoint.

	switch e.neigh.State {
	case Unknown, Incomplete, Failed:
		e.neigh.LinkAddr = remoteLinkAddr
		e.setStateLocked(Stale)
		e.notifyWakersLocked()
		e.dispatchAddEventLocked()

	case Reachable, Delay, Probe:
		if e.neigh.LinkAddr != remoteLinkAddr {
			e.neigh.LinkAddr = remoteLinkAddr
			e.setStateLocked(Stale)
			e.dispatchChangeEventLocked()
		}

	case Stale:
		if e.neigh.LinkAddr != remoteLinkAddr {
			e.neigh.LinkAddr = remoteLinkAddr
			e.dispatchChangeEventLocked()
		}

	case Static:
		// Do nothing

	default:
		panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
	}
}

// handleConfirmationLocked processes an incoming neighbor confirmation
// (e.g. ARP reply or Neighbor Advertisement for ARP or NDP, respectively).
//
// Follows the state machine defined by RFC 4861 section 7.2.5.
//
// TODO(gvisor.dev/issue/2277): To protect against ARP poisoning and other
// attacks against NDP functions, Secure Neighbor Discovery (SEND) Protocol
// should be deployed where preventing access to the broadcast segment might
// not be possible. SEND uses RSA key pairs to produce Cryptographically
// Generated Addresses (CGA), as defined in RFC 3972. This ensures that the
// claimed source of an NDP message is the owner of the claimed address.
func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) {
	switch e.neigh.State {
	case Incomplete:
		if len(linkAddr) == 0 {
			// "If the link layer has addresses and no Target Link-Layer Address
			// option is included, the receiving node SHOULD silently discard the
			// received advertisement." - RFC 4861 section 7.2.5
			break
		}

		e.neigh.LinkAddr = linkAddr
		if flags.Solicited {
			e.setStateLocked(Reachable)
		} else {
			e.setStateLocked(Stale)
		}
		e.dispatchChangeEventLocked()
		e.isRouter = flags.IsRouter
		e.notifyWakersLocked()

		// "Note that the Override flag is ignored if the entry is in the
		// INCOMPLETE state." - RFC 4861 section 7.2.5

	case Reachable, Stale, Delay, Probe:
		isLinkAddrDifferent := len(linkAddr) != 0 && e.neigh.LinkAddr != linkAddr

		if isLinkAddrDifferent {
			if !flags.Override {
				if e.neigh.State == Reachable {
					e.setStateLocked(Stale)
					e.dispatchChangeEventLocked()
				}
				break
			}

			e.neigh.LinkAddr = linkAddr

			if !flags.Solicited {
				if e.neigh.State != Stale {
					e.setStateLocked(Stale)
					e.dispatchChangeEventLocked()
				} else {
					// Notify the LinkAddr change, even though NUD state hasn't changed.
					e.dispatchChangeEventLocked()
				}
				break
			}
		}

		if flags.Solicited && (flags.Override || !isLinkAddrDifferent) {
			wasReachable := e.neigh.State == Reachable
			// Set state to Reachable again to refresh timers.
			e.setStateLocked(Reachable)
			e.notifyWakersLocked()
			if !wasReachable {
				e.dispatchChangeEventLocked()
			}
		}

		if e.isRouter && !flags.IsRouter && header.IsV6UnicastAddress(e.neigh.Addr) {
			// "In those cases where the IsRouter flag changes from TRUE to FALSE as
			// a result of this update, the node MUST remove that router from the
			// Default Router List and update the Destination Cache entries for all
			// destinations using that neighbor as a router as specified in Section
			// 7.3.3.  This is needed to detect when a node that is used as a router
			// stops forwarding packets due to being configured as a host."
			//  - RFC 4861 section 7.2.5
			//
			// TODO(gvisor.dev/issue/4085): Remove the special casing we do for IPv6
			// here.
			ep, ok := e.nic.networkEndpoints[header.IPv6ProtocolNumber]
			if !ok {
				panic(fmt.Sprintf("have a neighbor entry for an IPv6 router but no IPv6 network endpoint"))
			}

			if ndpEP, ok := ep.(NDPEndpoint); ok {
				ndpEP.InvalidateDefaultRouter(e.neigh.Addr)
			}
		}
		e.isRouter = flags.IsRouter

	case Unknown, Failed, Static:
		// Do nothing

	default:
		panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
	}
}

// handleUpperLevelConfirmationLocked processes an incoming upper-level protocol
// (e.g. TCP acknowledgements) reachability confirmation.
func (e *neighborEntry) handleUpperLevelConfirmationLocked() {
	switch e.neigh.State {
	case Reachable, Stale, Delay, Probe:
		wasReachable := e.neigh.State == Reachable
		// Set state to Reachable again to refresh timers.
		e.setStateLocked(Reachable)
		if !wasReachable {
			e.dispatchChangeEventLocked()
		}

	case Unknown, Incomplete, Failed, Static:
		// Do nothing

	default:
		panic(fmt.Sprintf("Invalid cache entry state: %s", e.neigh.State))
	}
}