| // 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" |
| |
| "gvisor.dev/gvisor/pkg/sync" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| ) |
| |
| var _ AddressableEndpoint = (*AddressableEndpointState)(nil) |
| |
| // AddressableEndpointState is an implementation of an AddressableEndpoint. |
| type AddressableEndpointState struct { |
| networkEndpoint NetworkEndpoint |
| |
| // Lock ordering (from outer to inner lock ordering): |
| // |
| // AddressableEndpointState.mu |
| // addressState.mu |
| mu struct { |
| sync.RWMutex |
| |
| endpoints map[tcpip.Address]*addressState |
| primary []*addressState |
| } |
| } |
| |
| // Init initializes the AddressableEndpointState with networkEndpoint. |
| // |
| // Must be called before calling any other function on m. |
| func (a *AddressableEndpointState) Init(networkEndpoint NetworkEndpoint) { |
| a.networkEndpoint = networkEndpoint |
| |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| a.mu.endpoints = make(map[tcpip.Address]*addressState) |
| } |
| |
| // GetAddress returns the AddressEndpoint for the passed address. |
| // |
| // GetAddress does not increment the address's reference count or check if the |
| // address is considered bound to the endpoint. |
| // |
| // Returns nil if the passed address is not associated with the endpoint. |
| func (a *AddressableEndpointState) GetAddress(addr tcpip.Address) AddressEndpoint { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| ep, ok := a.mu.endpoints[addr] |
| if !ok { |
| return nil |
| } |
| return ep |
| } |
| |
| // ForEachEndpoint calls f for each address. |
| // |
| // Once f returns false, f will no longer be called. |
| func (a *AddressableEndpointState) ForEachEndpoint(f func(AddressEndpoint) bool) { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| for _, ep := range a.mu.endpoints { |
| if !f(ep) { |
| return |
| } |
| } |
| } |
| |
| // ForEachPrimaryEndpoint calls f for each primary address. |
| // |
| // Once f returns false, f will no longer be called. |
| func (a *AddressableEndpointState) ForEachPrimaryEndpoint(f func(AddressEndpoint) bool) { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| for _, ep := range a.mu.primary { |
| if !f(ep) { |
| return |
| } |
| } |
| } |
| |
| func (a *AddressableEndpointState) releaseAddressState(addrState *addressState) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| a.releaseAddressStateLocked(addrState) |
| } |
| |
| // releaseAddressState removes addrState from s's address state (primary and endpoints list). |
| // |
| // Preconditions: a.mu must be write locked. |
| func (a *AddressableEndpointState) releaseAddressStateLocked(addrState *addressState) { |
| oldPrimary := a.mu.primary |
| for i, s := range a.mu.primary { |
| if s == addrState { |
| a.mu.primary = append(a.mu.primary[:i], a.mu.primary[i+1:]...) |
| oldPrimary[len(oldPrimary)-1] = nil |
| break |
| } |
| } |
| delete(a.mu.endpoints, addrState.addr.Address) |
| } |
| |
| // AddAndAcquirePermanentAddress implements AddressableEndpoint. |
| func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior, configType AddressConfigType, deprecated bool) (AddressEndpoint, tcpip.Error) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| ep, err := a.addAndAcquireAddressLocked(addr, peb, configType, deprecated, true /* permanent */) |
| // From https://golang.org/doc/faq#nil_error: |
| // |
| // Under the covers, interfaces are implemented as two elements, a type T and |
| // a value V. |
| // |
| // An interface value is nil only if the V and T are both unset, (T=nil, V is |
| // not set), In particular, a nil interface will always hold a nil type. If we |
| // store a nil pointer of type *int inside an interface value, the inner type |
| // will be *int regardless of the value of the pointer: (T=*int, V=nil). Such |
| // an interface value will therefore be non-nil even when the pointer value V |
| // inside is nil. |
| // |
| // Since addAndAcquireAddressLocked returns a nil value with a non-nil type, |
| // we need to explicitly return nil below if ep is (a typed) nil. |
| if ep == nil { |
| return nil, err |
| } |
| return ep, err |
| } |
| |
| // AddAndAcquireTemporaryAddress adds a temporary address. |
| // |
| // Returns *tcpip.ErrDuplicateAddress if the address exists. |
| // |
| // The temporary address's endpoint is acquired and returned. |
| func (a *AddressableEndpointState) AddAndAcquireTemporaryAddress(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior) (AddressEndpoint, tcpip.Error) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| ep, err := a.addAndAcquireAddressLocked(addr, peb, AddressConfigStatic, false /* deprecated */, false /* permanent */) |
| // From https://golang.org/doc/faq#nil_error: |
| // |
| // Under the covers, interfaces are implemented as two elements, a type T and |
| // a value V. |
| // |
| // An interface value is nil only if the V and T are both unset, (T=nil, V is |
| // not set), In particular, a nil interface will always hold a nil type. If we |
| // store a nil pointer of type *int inside an interface value, the inner type |
| // will be *int regardless of the value of the pointer: (T=*int, V=nil). Such |
| // an interface value will therefore be non-nil even when the pointer value V |
| // inside is nil. |
| // |
| // Since addAndAcquireAddressLocked returns a nil value with a non-nil type, |
| // we need to explicitly return nil below if ep is (a typed) nil. |
| if ep == nil { |
| return nil, err |
| } |
| return ep, err |
| } |
| |
| // addAndAcquireAddressLocked adds, acquires and returns a permanent or |
| // temporary address. |
| // |
| // If the addressable endpoint already has the address in a non-permanent state, |
| // and addAndAcquireAddressLocked is adding a permanent address, that address is |
| // promoted in place and its properties set to the properties provided. If the |
| // address already exists in any other state, then *tcpip.ErrDuplicateAddress is |
| // returned, regardless the kind of address that is being added. |
| // |
| // Precondition: a.mu must be write locked. |
| func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior, configType AddressConfigType, deprecated, permanent bool) (*addressState, tcpip.Error) { |
| // attemptAddToPrimary is false when the address is already in the primary |
| // address list. |
| attemptAddToPrimary := true |
| addrState, ok := a.mu.endpoints[addr.Address] |
| if ok { |
| if !permanent { |
| // We are adding a non-permanent address but the address exists. No need |
| // to go any further since we can only promote existing temporary/expired |
| // addresses to permanent. |
| return nil, &tcpip.ErrDuplicateAddress{} |
| } |
| |
| addrState.mu.Lock() |
| if addrState.mu.kind.IsPermanent() { |
| addrState.mu.Unlock() |
| // We are adding a permanent address but a permanent address already |
| // exists. |
| return nil, &tcpip.ErrDuplicateAddress{} |
| } |
| |
| if addrState.mu.refs == 0 { |
| panic(fmt.Sprintf("found an address that should have been released (ref count == 0); address = %s", addrState.addr)) |
| } |
| |
| // We now promote the address. |
| for i, s := range a.mu.primary { |
| if s == addrState { |
| switch peb { |
| case CanBePrimaryEndpoint: |
| // The address is already in the primary address list. |
| attemptAddToPrimary = false |
| case FirstPrimaryEndpoint: |
| if i == 0 { |
| // The address is already first in the primary address list. |
| attemptAddToPrimary = false |
| } else { |
| a.mu.primary = append(a.mu.primary[:i], a.mu.primary[i+1:]...) |
| } |
| case NeverPrimaryEndpoint: |
| a.mu.primary = append(a.mu.primary[:i], a.mu.primary[i+1:]...) |
| default: |
| panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", peb)) |
| } |
| break |
| } |
| } |
| } |
| |
| if addrState == nil { |
| addrState = &addressState{ |
| addressableEndpointState: a, |
| addr: addr, |
| // Cache the subnet in addrState to avoid calls to addr.Subnet() as that |
| // results in allocations on every call. |
| subnet: addr.Subnet(), |
| } |
| a.mu.endpoints[addr.Address] = addrState |
| addrState.mu.Lock() |
| // We never promote an address to temporary - it can only be added as such. |
| // If we are actaully adding a permanent address, it is promoted below. |
| addrState.mu.kind = Temporary |
| } |
| |
| // At this point we have an address we are either promoting from an expired or |
| // temporary address to permanent, promoting an expired address to temporary, |
| // or we are adding a new temporary or permanent address. |
| // |
| // The address MUST be write locked at this point. |
| defer addrState.mu.Unlock() |
| |
| if permanent { |
| if addrState.mu.kind.IsPermanent() { |
| panic(fmt.Sprintf("only non-permanent addresses should be promoted to permanent; address = %s", addrState.addr)) |
| } |
| |
| // Primary addresses are biased by 1. |
| addrState.mu.refs++ |
| addrState.mu.kind = Permanent |
| } |
| // Acquire the address before returning it. |
| addrState.mu.refs++ |
| addrState.mu.deprecated = deprecated |
| addrState.mu.configType = configType |
| |
| if attemptAddToPrimary { |
| switch peb { |
| case NeverPrimaryEndpoint: |
| case CanBePrimaryEndpoint: |
| a.mu.primary = append(a.mu.primary, addrState) |
| case FirstPrimaryEndpoint: |
| if cap(a.mu.primary) == len(a.mu.primary) { |
| a.mu.primary = append([]*addressState{addrState}, a.mu.primary...) |
| } else { |
| // Shift all the endpoints by 1 to make room for the new address at the |
| // front. We could have just created a new slice but this saves |
| // allocations when the slice has capacity for the new address. |
| primaryCount := len(a.mu.primary) |
| a.mu.primary = append(a.mu.primary, nil) |
| if n := copy(a.mu.primary[1:], a.mu.primary); n != primaryCount { |
| panic(fmt.Sprintf("copied %d elements; expected = %d elements", n, primaryCount)) |
| } |
| a.mu.primary[0] = addrState |
| } |
| default: |
| panic(fmt.Sprintf("unrecognized primary endpoint behaviour = %d", peb)) |
| } |
| } |
| |
| return addrState, nil |
| } |
| |
| // RemovePermanentAddress implements AddressableEndpoint. |
| func (a *AddressableEndpointState) RemovePermanentAddress(addr tcpip.Address) tcpip.Error { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| return a.removePermanentAddressLocked(addr) |
| } |
| |
| // removePermanentAddressLocked is like RemovePermanentAddress but with locking |
| // requirements. |
| // |
| // Precondition: a.mu must be write locked. |
| func (a *AddressableEndpointState) removePermanentAddressLocked(addr tcpip.Address) tcpip.Error { |
| addrState, ok := a.mu.endpoints[addr] |
| if !ok { |
| return &tcpip.ErrBadLocalAddress{} |
| } |
| |
| return a.removePermanentEndpointLocked(addrState) |
| } |
| |
| // RemovePermanentEndpoint removes the passed endpoint if it is associated with |
| // a and permanent. |
| func (a *AddressableEndpointState) RemovePermanentEndpoint(ep AddressEndpoint) tcpip.Error { |
| addrState, ok := ep.(*addressState) |
| if !ok || addrState.addressableEndpointState != a { |
| return &tcpip.ErrInvalidEndpointState{} |
| } |
| |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| return a.removePermanentEndpointLocked(addrState) |
| } |
| |
| // removePermanentAddressLocked is like RemovePermanentAddress but with locking |
| // requirements. |
| // |
| // Precondition: a.mu must be write locked. |
| func (a *AddressableEndpointState) removePermanentEndpointLocked(addrState *addressState) tcpip.Error { |
| if !addrState.GetKind().IsPermanent() { |
| return &tcpip.ErrBadLocalAddress{} |
| } |
| |
| addrState.SetKind(PermanentExpired) |
| a.decAddressRefLocked(addrState) |
| return nil |
| } |
| |
| // decAddressRef decrements the address's reference count and releases it once |
| // the reference count hits 0. |
| func (a *AddressableEndpointState) decAddressRef(addrState *addressState) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| a.decAddressRefLocked(addrState) |
| } |
| |
| // decAddressRefLocked is like decAddressRef but with locking requirements. |
| // |
| // Precondition: a.mu must be write locked. |
| func (a *AddressableEndpointState) decAddressRefLocked(addrState *addressState) { |
| addrState.mu.Lock() |
| defer addrState.mu.Unlock() |
| |
| if addrState.mu.refs == 0 { |
| panic(fmt.Sprintf("attempted to decrease ref count for AddressEndpoint w/ addr = %s when it is already released", addrState.addr)) |
| } |
| |
| addrState.mu.refs-- |
| |
| if addrState.mu.refs != 0 { |
| return |
| } |
| |
| // A non-expired permanent address must not have its reference count dropped |
| // to 0. |
| if addrState.mu.kind.IsPermanent() { |
| panic(fmt.Sprintf("permanent addresses should be removed through the AddressableEndpoint: addr = %s, kind = %d", addrState.addr, addrState.mu.kind)) |
| } |
| |
| a.releaseAddressStateLocked(addrState) |
| } |
| |
| // MainAddress implements AddressableEndpoint. |
| func (a *AddressableEndpointState) MainAddress() tcpip.AddressWithPrefix { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| ep := a.acquirePrimaryAddressRLocked(func(ep *addressState) bool { |
| return ep.GetKind() == Permanent |
| }) |
| if ep == nil { |
| return tcpip.AddressWithPrefix{} |
| } |
| |
| addr := ep.AddressWithPrefix() |
| a.decAddressRefLocked(ep) |
| return addr |
| } |
| |
| // acquirePrimaryAddressRLocked returns an acquired primary address that is |
| // valid according to isValid. |
| // |
| // Precondition: e.mu must be read locked |
| func (a *AddressableEndpointState) acquirePrimaryAddressRLocked(isValid func(*addressState) bool) *addressState { |
| var deprecatedEndpoint *addressState |
| for _, ep := range a.mu.primary { |
| if !isValid(ep) { |
| continue |
| } |
| |
| if !ep.Deprecated() { |
| if ep.IncRef() { |
| // ep is not deprecated, so return it immediately. |
| // |
| // If we kept track of a deprecated endpoint, decrement its reference |
| // count since it was incremented when we decided to keep track of it. |
| if deprecatedEndpoint != nil { |
| a.decAddressRefLocked(deprecatedEndpoint) |
| deprecatedEndpoint = nil |
| } |
| |
| return ep |
| } |
| } else if deprecatedEndpoint == nil && ep.IncRef() { |
| // We prefer an endpoint that is not deprecated, but we keep track of |
| // ep in case a doesn't have any non-deprecated endpoints. |
| // |
| // If we end up finding a more preferred endpoint, ep's reference count |
| // will be decremented. |
| deprecatedEndpoint = ep |
| } |
| } |
| |
| return deprecatedEndpoint |
| } |
| |
| // AcquireAssignedAddressOrMatching returns an address endpoint that is |
| // considered assigned to the addressable endpoint. |
| // |
| // If the address is an exact match with an existing address, that address is |
| // returned. Otherwise, if f is provided, f is called with each address and |
| // the address that f returns true for is returned. |
| // |
| // If there is no matching address, a temporary address will be returned if |
| // allowTemp is true. |
| // |
| // Regardless how the address was obtained, it will be acquired before it is |
| // returned. |
| func (a *AddressableEndpointState) AcquireAssignedAddressOrMatching(localAddr tcpip.Address, f func(AddressEndpoint) bool, allowTemp bool, tempPEB PrimaryEndpointBehavior) AddressEndpoint { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| |
| if addrState, ok := a.mu.endpoints[localAddr]; ok { |
| if !addrState.IsAssigned(allowTemp) { |
| return nil |
| } |
| |
| if !addrState.IncRef() { |
| panic(fmt.Sprintf("failed to increase the reference count for address = %s", addrState.addr)) |
| } |
| |
| return addrState |
| } |
| |
| if f != nil { |
| for _, addrState := range a.mu.endpoints { |
| if addrState.IsAssigned(allowTemp) && f(addrState) && addrState.IncRef() { |
| return addrState |
| } |
| } |
| } |
| |
| if !allowTemp { |
| return nil |
| } |
| |
| addr := localAddr.WithPrefix() |
| ep, err := a.addAndAcquireAddressLocked(addr, tempPEB, AddressConfigStatic, false /* deprecated */, false /* permanent */) |
| if err != nil { |
| // addAndAcquireAddressLocked only returns an error if the address is |
| // already assigned but we just checked above if the address exists so we |
| // expect no error. |
| panic(fmt.Sprintf("a.addAndAcquireAddressLocked(%s, %d, %d, false, false): %s", addr, tempPEB, AddressConfigStatic, err)) |
| } |
| // From https://golang.org/doc/faq#nil_error: |
| // |
| // Under the covers, interfaces are implemented as two elements, a type T and |
| // a value V. |
| // |
| // An interface value is nil only if the V and T are both unset, (T=nil, V is |
| // not set), In particular, a nil interface will always hold a nil type. If we |
| // store a nil pointer of type *int inside an interface value, the inner type |
| // will be *int regardless of the value of the pointer: (T=*int, V=nil). Such |
| // an interface value will therefore be non-nil even when the pointer value V |
| // inside is nil. |
| // |
| // Since addAndAcquireAddressLocked returns a nil value with a non-nil type, |
| // we need to explicitly return nil below if ep is (a typed) nil. |
| if ep == nil { |
| return nil |
| } |
| return ep |
| } |
| |
| // AcquireAssignedAddress implements AddressableEndpoint. |
| func (a *AddressableEndpointState) AcquireAssignedAddress(localAddr tcpip.Address, allowTemp bool, tempPEB PrimaryEndpointBehavior) AddressEndpoint { |
| return a.AcquireAssignedAddressOrMatching(localAddr, nil, allowTemp, tempPEB) |
| } |
| |
| // AcquireOutgoingPrimaryAddress implements AddressableEndpoint. |
| func (a *AddressableEndpointState) AcquireOutgoingPrimaryAddress(remoteAddr tcpip.Address, allowExpired bool) AddressEndpoint { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| ep := a.acquirePrimaryAddressRLocked(func(ep *addressState) bool { |
| return ep.IsAssigned(allowExpired) |
| }) |
| |
| // From https://golang.org/doc/faq#nil_error: |
| // |
| // Under the covers, interfaces are implemented as two elements, a type T and |
| // a value V. |
| // |
| // An interface value is nil only if the V and T are both unset, (T=nil, V is |
| // not set), In particular, a nil interface will always hold a nil type. If we |
| // store a nil pointer of type *int inside an interface value, the inner type |
| // will be *int regardless of the value of the pointer: (T=*int, V=nil). Such |
| // an interface value will therefore be non-nil even when the pointer value V |
| // inside is nil. |
| // |
| // Since acquirePrimaryAddressRLocked returns a nil value with a non-nil type, |
| // we need to explicitly return nil below if ep is (a typed) nil. |
| if ep == nil { |
| return nil |
| } |
| |
| return ep |
| } |
| |
| // PrimaryAddresses implements AddressableEndpoint. |
| func (a *AddressableEndpointState) PrimaryAddresses() []tcpip.AddressWithPrefix { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| var addrs []tcpip.AddressWithPrefix |
| for _, ep := range a.mu.primary { |
| // Don't include tentative, expired or temporary endpoints |
| // to avoid confusion and prevent the caller from using |
| // those. |
| switch ep.GetKind() { |
| case PermanentTentative, PermanentExpired, Temporary: |
| continue |
| } |
| |
| addrs = append(addrs, ep.AddressWithPrefix()) |
| } |
| |
| return addrs |
| } |
| |
| // PermanentAddresses implements AddressableEndpoint. |
| func (a *AddressableEndpointState) PermanentAddresses() []tcpip.AddressWithPrefix { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| |
| var addrs []tcpip.AddressWithPrefix |
| for _, ep := range a.mu.endpoints { |
| if !ep.GetKind().IsPermanent() { |
| continue |
| } |
| |
| addrs = append(addrs, ep.AddressWithPrefix()) |
| } |
| |
| return addrs |
| } |
| |
| // Cleanup forcefully leaves all groups and removes all permanent addresses. |
| func (a *AddressableEndpointState) Cleanup() { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| |
| for _, ep := range a.mu.endpoints { |
| // removePermanentEndpointLocked returns *tcpip.ErrBadLocalAddress if ep is |
| // not a permanent address. |
| switch err := a.removePermanentEndpointLocked(ep); err.(type) { |
| case nil, *tcpip.ErrBadLocalAddress: |
| default: |
| panic(fmt.Sprintf("unexpected error from removePermanentEndpointLocked(%s): %s", ep.addr, err)) |
| } |
| } |
| } |
| |
| var _ AddressEndpoint = (*addressState)(nil) |
| |
| // addressState holds state for an address. |
| type addressState struct { |
| addressableEndpointState *AddressableEndpointState |
| addr tcpip.AddressWithPrefix |
| subnet tcpip.Subnet |
| // Lock ordering (from outer to inner lock ordering): |
| // |
| // AddressableEndpointState.mu |
| // addressState.mu |
| mu struct { |
| sync.RWMutex |
| |
| refs uint32 |
| kind AddressKind |
| configType AddressConfigType |
| deprecated bool |
| } |
| } |
| |
| // AddressWithPrefix implements AddressEndpoint. |
| func (a *addressState) AddressWithPrefix() tcpip.AddressWithPrefix { |
| return a.addr |
| } |
| |
| // Subnet implements AddressEndpoint. |
| func (a *addressState) Subnet() tcpip.Subnet { |
| return a.subnet |
| } |
| |
| // GetKind implements AddressEndpoint. |
| func (a *addressState) GetKind() AddressKind { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| return a.mu.kind |
| } |
| |
| // SetKind implements AddressEndpoint. |
| func (a *addressState) SetKind(kind AddressKind) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| a.mu.kind = kind |
| } |
| |
| // IsAssigned implements AddressEndpoint. |
| func (a *addressState) IsAssigned(allowExpired bool) bool { |
| if !a.addressableEndpointState.networkEndpoint.Enabled() { |
| return false |
| } |
| |
| switch a.GetKind() { |
| case PermanentTentative: |
| return false |
| case PermanentExpired: |
| return allowExpired |
| default: |
| return true |
| } |
| } |
| |
| // IncRef implements AddressEndpoint. |
| func (a *addressState) IncRef() bool { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| if a.mu.refs == 0 { |
| return false |
| } |
| |
| a.mu.refs++ |
| return true |
| } |
| |
| // DecRef implements AddressEndpoint. |
| func (a *addressState) DecRef() { |
| a.addressableEndpointState.decAddressRef(a) |
| } |
| |
| // ConfigType implements AddressEndpoint. |
| func (a *addressState) ConfigType() AddressConfigType { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| return a.mu.configType |
| } |
| |
| // SetDeprecated implements AddressEndpoint. |
| func (a *addressState) SetDeprecated(d bool) { |
| a.mu.Lock() |
| defer a.mu.Unlock() |
| a.mu.deprecated = d |
| } |
| |
| // Deprecated implements AddressEndpoint. |
| func (a *addressState) Deprecated() bool { |
| a.mu.RLock() |
| defer a.mu.RUnlock() |
| return a.mu.deprecated |
| } |