gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/network/internal/ip/duplicate_address_detection.go (about)

     1  // Copyright 2021 The gVisor 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  // Package ip holds IPv4/IPv6 common utilities.
    16  package ip
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  	"io"
    22  
    23  	"gvisor.dev/gvisor/pkg/sync"
    24  	"gvisor.dev/gvisor/pkg/tcpip"
    25  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    26  )
    27  
    28  type extendRequest int
    29  
    30  const (
    31  	notRequested extendRequest = iota
    32  	requested
    33  	extended
    34  )
    35  
    36  type dadState struct {
    37  	nonce         []byte
    38  	extendRequest extendRequest
    39  
    40  	done  *bool
    41  	timer tcpip.Timer
    42  
    43  	completionHandlers []stack.DADCompletionHandler
    44  }
    45  
    46  // DADProtocol is a protocol whose core state machine can be represented by DAD.
    47  type DADProtocol interface {
    48  	// SendDADMessage attempts to send a DAD probe message.
    49  	SendDADMessage(tcpip.Address, []byte) tcpip.Error
    50  }
    51  
    52  // DADOptions holds options for DAD.
    53  type DADOptions struct {
    54  	Clock              tcpip.Clock
    55  	SecureRNG          io.Reader
    56  	NonceSize          uint8
    57  	ExtendDADTransmits uint8
    58  	Protocol           DADProtocol
    59  	NICID              tcpip.NICID
    60  }
    61  
    62  // DAD performs duplicate address detection for addresses.
    63  type DAD struct {
    64  	opts    DADOptions
    65  	configs stack.DADConfigurations
    66  
    67  	protocolMU sync.Locker
    68  	addresses  map[tcpip.Address]dadState
    69  }
    70  
    71  // Init initializes the DAD state.
    72  //
    73  // Must only be called once for the lifetime of d; Init will panic if it is
    74  // called twice.
    75  //
    76  // The lock will only be taken when timers fire.
    77  func (d *DAD) Init(protocolMU sync.Locker, configs stack.DADConfigurations, opts DADOptions) {
    78  	if d.addresses != nil {
    79  		panic("attempted to initialize DAD state twice")
    80  	}
    81  
    82  	if opts.NonceSize != 0 && opts.ExtendDADTransmits == 0 {
    83  		panic(fmt.Sprintf("given a non-zero value for NonceSize (%d) but zero for ExtendDADTransmits", opts.NonceSize))
    84  	}
    85  
    86  	configs.Validate()
    87  
    88  	*d = DAD{
    89  		opts:       opts,
    90  		configs:    configs,
    91  		protocolMU: protocolMU,
    92  		addresses:  make(map[tcpip.Address]dadState),
    93  	}
    94  }
    95  
    96  // CheckDuplicateAddressLocked performs DAD for an address, calling the
    97  // completion handler once DAD resolves.
    98  //
    99  // If DAD is already performing for the provided address, h will be called when
   100  // the currently running process completes.
   101  //
   102  // Precondition: d.protocolMU must be locked.
   103  func (d *DAD) CheckDuplicateAddressLocked(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
   104  	if d.configs.DupAddrDetectTransmits == 0 {
   105  		return stack.DADDisabled
   106  	}
   107  
   108  	ret := stack.DADAlreadyRunning
   109  	s, ok := d.addresses[addr]
   110  	if !ok {
   111  		ret = stack.DADStarting
   112  
   113  		remaining := d.configs.DupAddrDetectTransmits
   114  
   115  		// Protected by d.protocolMU.
   116  		done := false
   117  
   118  		s = dadState{
   119  			done: &done,
   120  			timer: d.opts.Clock.AfterFunc(0, func() {
   121  				dadDone := remaining == 0
   122  
   123  				nonce, earlyReturn := func() ([]byte, bool) {
   124  					d.protocolMU.Lock()
   125  					defer d.protocolMU.Unlock()
   126  
   127  					if done {
   128  						return nil, true
   129  					}
   130  
   131  					s, ok := d.addresses[addr]
   132  					if !ok {
   133  						panic(fmt.Sprintf("dad: timer fired but missing state for %s on NIC(%d)", addr, d.opts.NICID))
   134  					}
   135  
   136  					// As per RFC 7527 section 4
   137  					//
   138  					//   If any probe is looped back within RetransTimer milliseconds
   139  					//   after having sent DupAddrDetectTransmits NS(DAD) messages, the
   140  					//   interface continues with another MAX_MULTICAST_SOLICIT number of
   141  					//   NS(DAD) messages transmitted RetransTimer milliseconds apart.
   142  					if dadDone && s.extendRequest == requested {
   143  						dadDone = false
   144  						remaining = d.opts.ExtendDADTransmits
   145  						s.extendRequest = extended
   146  					}
   147  
   148  					if !dadDone && d.opts.NonceSize != 0 {
   149  						if s.nonce == nil {
   150  							s.nonce = make([]byte, d.opts.NonceSize)
   151  						}
   152  
   153  						if n, err := io.ReadFull(d.opts.SecureRNG, s.nonce); err != nil {
   154  							panic(fmt.Sprintf("SecureRNG.Read(...): %s", err))
   155  						} else if n != len(s.nonce) {
   156  							panic(fmt.Sprintf("expected to read %d bytes from secure RNG, only read %d bytes", len(s.nonce), n))
   157  						}
   158  					}
   159  
   160  					d.addresses[addr] = s
   161  					return s.nonce, false
   162  				}()
   163  				if earlyReturn {
   164  					return
   165  				}
   166  
   167  				var err tcpip.Error
   168  				if !dadDone {
   169  					err = d.opts.Protocol.SendDADMessage(addr, nonce)
   170  				}
   171  
   172  				d.protocolMU.Lock()
   173  				defer d.protocolMU.Unlock()
   174  
   175  				if done {
   176  					return
   177  				}
   178  
   179  				s, ok := d.addresses[addr]
   180  				if !ok {
   181  					panic(fmt.Sprintf("dad: timer fired but missing state for %s on NIC(%d)", addr, d.opts.NICID))
   182  				}
   183  
   184  				if !dadDone && err == nil {
   185  					remaining--
   186  					s.timer.Reset(d.configs.RetransmitTimer)
   187  					return
   188  				}
   189  
   190  				// At this point we know that either DAD has resolved or we hit an error
   191  				// sending the last DAD message. Either way, clear the DAD state.
   192  				done = false
   193  				s.timer.Stop()
   194  				delete(d.addresses, addr)
   195  
   196  				var res stack.DADResult = &stack.DADSucceeded{}
   197  				if err != nil {
   198  					res = &stack.DADError{Err: err}
   199  				}
   200  				for _, h := range s.completionHandlers {
   201  					h(res)
   202  				}
   203  			}),
   204  		}
   205  	}
   206  
   207  	s.completionHandlers = append(s.completionHandlers, h)
   208  	d.addresses[addr] = s
   209  	return ret
   210  }
   211  
   212  // ExtendIfNonceEqualLockedDisposition enumerates the possible results from
   213  // ExtendIfNonceEqualLocked.
   214  type ExtendIfNonceEqualLockedDisposition int
   215  
   216  const (
   217  	// Extended indicates that the DAD process was extended.
   218  	Extended ExtendIfNonceEqualLockedDisposition = iota
   219  
   220  	// AlreadyExtended indicates that the DAD process was already extended.
   221  	AlreadyExtended
   222  
   223  	// NoDADStateFound indicates that DAD state was not found for the address.
   224  	NoDADStateFound
   225  
   226  	// NonceDisabled indicates that nonce values are not sent with DAD messages.
   227  	NonceDisabled
   228  
   229  	// NonceNotEqual indicates that the nonce value passed and the nonce in the
   230  	// last send DAD message are not equal.
   231  	NonceNotEqual
   232  )
   233  
   234  // ExtendIfNonceEqualLocked extends the DAD process if the provided nonce is the
   235  // same as the nonce sent in the last DAD message.
   236  //
   237  // Precondition: d.protocolMU must be locked.
   238  func (d *DAD) ExtendIfNonceEqualLocked(addr tcpip.Address, nonce []byte) ExtendIfNonceEqualLockedDisposition {
   239  	s, ok := d.addresses[addr]
   240  	if !ok {
   241  		return NoDADStateFound
   242  	}
   243  
   244  	if d.opts.NonceSize == 0 {
   245  		return NonceDisabled
   246  	}
   247  
   248  	if s.extendRequest != notRequested {
   249  		return AlreadyExtended
   250  	}
   251  
   252  	// As per RFC 7527 section 4
   253  	//
   254  	//   If any probe is looped back within RetransTimer milliseconds after having
   255  	//   sent DupAddrDetectTransmits NS(DAD) messages, the interface continues
   256  	//   with another MAX_MULTICAST_SOLICIT number of NS(DAD) messages transmitted
   257  	//   RetransTimer milliseconds apart.
   258  	//
   259  	// If a DAD message has already been sent and the nonce value we observed is
   260  	// the same as the nonce value we last sent, then we assume our probe was
   261  	// looped back and request an extension to the DAD process.
   262  	//
   263  	// Note, the first DAD message is sent asynchronously so we need to make sure
   264  	// that we sent a DAD message by checking if we have a nonce value set.
   265  	if s.nonce != nil && bytes.Equal(s.nonce, nonce) {
   266  		s.extendRequest = requested
   267  		d.addresses[addr] = s
   268  		return Extended
   269  	}
   270  
   271  	return NonceNotEqual
   272  }
   273  
   274  // StopLocked stops a currently running DAD process.
   275  //
   276  // Precondition: d.protocolMU must be locked.
   277  func (d *DAD) StopLocked(addr tcpip.Address, reason stack.DADResult) {
   278  	s, ok := d.addresses[addr]
   279  	if !ok {
   280  		return
   281  	}
   282  
   283  	*s.done = true
   284  	s.timer.Stop()
   285  	delete(d.addresses, addr)
   286  
   287  	for _, h := range s.completionHandlers {
   288  		h(reason)
   289  	}
   290  }
   291  
   292  // SetConfigsLocked sets the DAD configurations.
   293  //
   294  // Precondition: d.protocolMU must be locked.
   295  func (d *DAD) SetConfigsLocked(c stack.DADConfigurations) {
   296  	c.Validate()
   297  	d.configs = c
   298  }