github.com/slackhq/nebula@v1.9.0/calculated_remote.go (about)

     1  package nebula
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/slackhq/nebula/cidr"
    10  	"github.com/slackhq/nebula/config"
    11  	"github.com/slackhq/nebula/iputil"
    12  )
    13  
    14  // This allows us to "guess" what the remote might be for a host while we wait
    15  // for the lighthouse response. See "lighthouse.calculated_remotes" in the
    16  // example config file.
    17  type calculatedRemote struct {
    18  	ipNet  net.IPNet
    19  	maskIP iputil.VpnIp
    20  	mask   iputil.VpnIp
    21  	port   uint32
    22  }
    23  
    24  func newCalculatedRemote(ipNet *net.IPNet, port int) (*calculatedRemote, error) {
    25  	// Ensure this is an IPv4 mask that we expect
    26  	ones, bits := ipNet.Mask.Size()
    27  	if ones == 0 || bits != 32 {
    28  		return nil, fmt.Errorf("invalid mask: %v", ipNet)
    29  	}
    30  	if port < 0 || port > math.MaxUint16 {
    31  		return nil, fmt.Errorf("invalid port: %d", port)
    32  	}
    33  
    34  	return &calculatedRemote{
    35  		ipNet:  *ipNet,
    36  		maskIP: iputil.Ip2VpnIp(ipNet.IP),
    37  		mask:   iputil.Ip2VpnIp(ipNet.Mask),
    38  		port:   uint32(port),
    39  	}, nil
    40  }
    41  
    42  func (c *calculatedRemote) String() string {
    43  	return fmt.Sprintf("CalculatedRemote(mask=%v port=%d)", c.ipNet, c.port)
    44  }
    45  
    46  func (c *calculatedRemote) Apply(ip iputil.VpnIp) *Ip4AndPort {
    47  	// Combine the masked bytes of the "mask" IP with the unmasked bytes
    48  	// of the overlay IP
    49  	masked := (c.maskIP & c.mask) | (ip & ^c.mask)
    50  
    51  	return &Ip4AndPort{Ip: uint32(masked), Port: c.port}
    52  }
    53  
    54  func NewCalculatedRemotesFromConfig(c *config.C, k string) (*cidr.Tree4[[]*calculatedRemote], error) {
    55  	value := c.Get(k)
    56  	if value == nil {
    57  		return nil, nil
    58  	}
    59  
    60  	calculatedRemotes := cidr.NewTree4[[]*calculatedRemote]()
    61  
    62  	rawMap, ok := value.(map[any]any)
    63  	if !ok {
    64  		return nil, fmt.Errorf("config `%s` has invalid type: %T", k, value)
    65  	}
    66  	for rawKey, rawValue := range rawMap {
    67  		rawCIDR, ok := rawKey.(string)
    68  		if !ok {
    69  			return nil, fmt.Errorf("config `%s` has invalid key (type %T): %v", k, rawKey, rawKey)
    70  		}
    71  
    72  		_, ipNet, err := net.ParseCIDR(rawCIDR)
    73  		if err != nil {
    74  			return nil, fmt.Errorf("config `%s` has invalid CIDR: %s", k, rawCIDR)
    75  		}
    76  
    77  		entry, err := newCalculatedRemotesListFromConfig(rawValue)
    78  		if err != nil {
    79  			return nil, fmt.Errorf("config '%s.%s': %w", k, rawCIDR, err)
    80  		}
    81  
    82  		calculatedRemotes.AddCIDR(ipNet, entry)
    83  	}
    84  
    85  	return calculatedRemotes, nil
    86  }
    87  
    88  func newCalculatedRemotesListFromConfig(raw any) ([]*calculatedRemote, error) {
    89  	rawList, ok := raw.([]any)
    90  	if !ok {
    91  		return nil, fmt.Errorf("calculated_remotes entry has invalid type: %T", raw)
    92  	}
    93  
    94  	var l []*calculatedRemote
    95  	for _, e := range rawList {
    96  		c, err := newCalculatedRemotesEntryFromConfig(e)
    97  		if err != nil {
    98  			return nil, fmt.Errorf("calculated_remotes entry: %w", err)
    99  		}
   100  		l = append(l, c)
   101  	}
   102  
   103  	return l, nil
   104  }
   105  
   106  func newCalculatedRemotesEntryFromConfig(raw any) (*calculatedRemote, error) {
   107  	rawMap, ok := raw.(map[any]any)
   108  	if !ok {
   109  		return nil, fmt.Errorf("invalid type: %T", raw)
   110  	}
   111  
   112  	rawValue := rawMap["mask"]
   113  	if rawValue == nil {
   114  		return nil, fmt.Errorf("missing mask: %v", rawMap)
   115  	}
   116  	rawMask, ok := rawValue.(string)
   117  	if !ok {
   118  		return nil, fmt.Errorf("invalid mask (type %T): %v", rawValue, rawValue)
   119  	}
   120  	_, ipNet, err := net.ParseCIDR(rawMask)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("invalid mask: %s", rawMask)
   123  	}
   124  
   125  	var port int
   126  	rawValue = rawMap["port"]
   127  	if rawValue == nil {
   128  		return nil, fmt.Errorf("missing port: %v", rawMap)
   129  	}
   130  	switch v := rawValue.(type) {
   131  	case int:
   132  		port = v
   133  	case string:
   134  		port, err = strconv.Atoi(v)
   135  		if err != nil {
   136  			return nil, fmt.Errorf("invalid port: %s: %w", v, err)
   137  		}
   138  	default:
   139  		return nil, fmt.Errorf("invalid port (type %T): %v", rawValue, rawValue)
   140  	}
   141  
   142  	return newCalculatedRemote(ipNet, port)
   143  }