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 }