|
@@ -6,6 +6,7 @@ import (
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"net"
|
|
|
+ "net/netip"
|
|
|
"sync"
|
|
|
"sync/atomic"
|
|
|
"time"
|
|
@@ -33,6 +34,7 @@ type netIpAndPort struct {
|
|
|
type LightHouse struct {
|
|
|
//TODO: We need a timer wheel to kick out vpnIps that haven't reported in a long time
|
|
|
sync.RWMutex //Because we concurrently read and write to our maps
|
|
|
+ ctx context.Context
|
|
|
amLighthouse bool
|
|
|
myVpnIp iputil.VpnIp
|
|
|
myVpnZeros iputil.VpnIp
|
|
@@ -82,7 +84,7 @@ type LightHouse struct {
|
|
|
|
|
|
// NewLightHouseFromConfig will build a Lighthouse struct from the values provided in the config object
|
|
|
// addrMap should be nil unless this is during a config reload
|
|
|
-func NewLightHouseFromConfig(l *logrus.Logger, c *config.C, myVpnNet *net.IPNet, pc *udp.Conn, p *Punchy) (*LightHouse, error) {
|
|
|
+func NewLightHouseFromConfig(ctx context.Context, l *logrus.Logger, c *config.C, myVpnNet *net.IPNet, pc *udp.Conn, p *Punchy) (*LightHouse, error) {
|
|
|
amLighthouse := c.GetBool("lighthouse.am_lighthouse", false)
|
|
|
nebulaPort := uint32(c.GetInt("listen.port", 0))
|
|
|
if amLighthouse && nebulaPort == 0 {
|
|
@@ -100,6 +102,7 @@ func NewLightHouseFromConfig(l *logrus.Logger, c *config.C, myVpnNet *net.IPNet,
|
|
|
|
|
|
ones, _ := myVpnNet.Mask.Size()
|
|
|
h := LightHouse{
|
|
|
+ ctx: ctx,
|
|
|
amLighthouse: amLighthouse,
|
|
|
myVpnIp: iputil.Ip2VpnIp(myVpnNet.IP),
|
|
|
myVpnZeros: iputil.VpnIp(32 - ones),
|
|
@@ -258,7 +261,7 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|
|
}
|
|
|
|
|
|
//NOTE: many things will get much simpler when we combine static_host_map and lighthouse.hosts in config
|
|
|
- if initial || c.HasChanged("static_host_map") {
|
|
|
+ if initial || c.HasChanged("static_host_map") || c.HasChanged("static_map.cadence") || c.HasChanged("static_map.network") || c.HasChanged("static_map.lookup_timeout") {
|
|
|
staticList := make(map[iputil.VpnIp]struct{})
|
|
|
err := lh.loadStaticMap(c, lh.myVpnNet, staticList)
|
|
|
if err != nil {
|
|
@@ -268,9 +271,19 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|
|
lh.staticList.Store(&staticList)
|
|
|
if !initial {
|
|
|
//TODO: we should remove any remote list entries for static hosts that were removed/modified?
|
|
|
- lh.l.Info("static_host_map has changed")
|
|
|
+ if c.HasChanged("static_host_map") {
|
|
|
+ lh.l.Info("static_host_map has changed")
|
|
|
+ }
|
|
|
+ if c.HasChanged("static_map.cadence") {
|
|
|
+ lh.l.Info("static_map.cadence has changed")
|
|
|
+ }
|
|
|
+ if c.HasChanged("static_map.network") {
|
|
|
+ lh.l.Info("static_map.network has changed")
|
|
|
+ }
|
|
|
+ if c.HasChanged("static_map.lookup_timeout") {
|
|
|
+ lh.l.Info("static_map.lookup_timeout has changed")
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
if initial || c.HasChanged("lighthouse.hosts") {
|
|
@@ -344,7 +357,48 @@ func (lh *LightHouse) parseLighthouses(c *config.C, tunCidr *net.IPNet, lhMap ma
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func getStaticMapCadence(c *config.C) (time.Duration, error) {
|
|
|
+ cadence := c.GetString("static_map.cadence", "30s")
|
|
|
+ d, err := time.ParseDuration(cadence)
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
+ return d, nil
|
|
|
+}
|
|
|
+
|
|
|
+func getStaticMapLookupTimeout(c *config.C) (time.Duration, error) {
|
|
|
+ lookupTimeout := c.GetString("static_map.lookup_timeout", "250ms")
|
|
|
+ d, err := time.ParseDuration(lookupTimeout)
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
+ return d, nil
|
|
|
+}
|
|
|
+
|
|
|
+func getStaticMapNetwork(c *config.C) (string, error) {
|
|
|
+ network := c.GetString("static_map.network", "ip4")
|
|
|
+ if network != "ip" && network != "ip4" && network != "ip6" {
|
|
|
+ return "", fmt.Errorf("static_map.network must be one of ip, ip4, or ip6")
|
|
|
+ }
|
|
|
+ return network, nil
|
|
|
+}
|
|
|
+
|
|
|
func (lh *LightHouse) loadStaticMap(c *config.C, tunCidr *net.IPNet, staticList map[iputil.VpnIp]struct{}) error {
|
|
|
+ d, err := getStaticMapCadence(c)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ network, err := getStaticMapNetwork(c)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ lookup_timeout, err := getStaticMapLookupTimeout(c)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
shm := c.GetMap("static_host_map", map[interface{}]interface{}{})
|
|
|
i := 0
|
|
|
|
|
@@ -360,21 +414,17 @@ func (lh *LightHouse) loadStaticMap(c *config.C, tunCidr *net.IPNet, staticList
|
|
|
|
|
|
vpnIp := iputil.Ip2VpnIp(rip)
|
|
|
vals, ok := v.([]interface{})
|
|
|
- if ok {
|
|
|
- for _, v := range vals {
|
|
|
- ip, port, err := udp.ParseIPAndPort(fmt.Sprintf("%v", v))
|
|
|
- if err != nil {
|
|
|
- return util.NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp, "entry": i + 1}, err)
|
|
|
- }
|
|
|
- lh.addStaticRemote(vpnIp, udp.NewAddr(ip, port), staticList)
|
|
|
- }
|
|
|
+ if !ok {
|
|
|
+ vals = []interface{}{v}
|
|
|
+ }
|
|
|
+ remoteAddrs := []string{}
|
|
|
+ for _, v := range vals {
|
|
|
+ remoteAddrs = append(remoteAddrs, fmt.Sprintf("%v", v))
|
|
|
+ }
|
|
|
|
|
|
- } else {
|
|
|
- ip, port, err := udp.ParseIPAndPort(fmt.Sprintf("%v", v))
|
|
|
- if err != nil {
|
|
|
- return util.NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp, "entry": i + 1}, err)
|
|
|
- }
|
|
|
- lh.addStaticRemote(vpnIp, udp.NewAddr(ip, port), staticList)
|
|
|
+ err := lh.addStaticRemotes(i, d, network, lookup_timeout, vpnIp, remoteAddrs, staticList)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
i++
|
|
|
}
|
|
@@ -482,30 +532,47 @@ func (lh *LightHouse) DeleteVpnIp(vpnIp iputil.VpnIp) {
|
|
|
// We are the owner because we don't want a lighthouse server to advertise for static hosts it was configured with
|
|
|
// And we don't want a lighthouse query reply to interfere with our learned cache if we are a client
|
|
|
// NOTE: this function should not interact with any hot path objects, like lh.staticList, the caller should handle it
|
|
|
-func (lh *LightHouse) addStaticRemote(vpnIp iputil.VpnIp, toAddr *udp.Addr, staticList map[iputil.VpnIp]struct{}) {
|
|
|
+func (lh *LightHouse) addStaticRemotes(i int, d time.Duration, network string, timeout time.Duration, vpnIp iputil.VpnIp, toAddrs []string, staticList map[iputil.VpnIp]struct{}) error {
|
|
|
lh.Lock()
|
|
|
am := lh.unlockedGetRemoteList(vpnIp)
|
|
|
am.Lock()
|
|
|
defer am.Unlock()
|
|
|
+ ctx := lh.ctx
|
|
|
lh.Unlock()
|
|
|
|
|
|
- if ipv4 := toAddr.IP.To4(); ipv4 != nil {
|
|
|
- to := NewIp4AndPort(ipv4, uint32(toAddr.Port))
|
|
|
- if !lh.unlockedShouldAddV4(vpnIp, to) {
|
|
|
- return
|
|
|
- }
|
|
|
- am.unlockedPrependV4(lh.myVpnIp, to)
|
|
|
+ hr, err := NewHostnameResults(ctx, lh.l, d, network, timeout, toAddrs, func() {
|
|
|
+ // This callback runs whenever the DNS hostname resolver finds a different set of IP's
|
|
|
+ // in its resolution for hostnames.
|
|
|
+ am.Lock()
|
|
|
+ defer am.Unlock()
|
|
|
+ am.shouldRebuild = true
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return util.NewContextualError("Static host address could not be parsed", m{"vpnIp": vpnIp, "entry": i + 1}, err)
|
|
|
+ }
|
|
|
+ am.unlockedSetHostnamesResults(hr)
|
|
|
|
|
|
- } else {
|
|
|
- to := NewIp6AndPort(toAddr.IP, uint32(toAddr.Port))
|
|
|
- if !lh.unlockedShouldAddV6(vpnIp, to) {
|
|
|
- return
|
|
|
+ for _, addrPort := range hr.GetIPs() {
|
|
|
+
|
|
|
+ switch {
|
|
|
+ case addrPort.Addr().Is4():
|
|
|
+ to := NewIp4AndPortFromNetIP(addrPort.Addr(), addrPort.Port())
|
|
|
+ if !lh.unlockedShouldAddV4(vpnIp, to) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ am.unlockedPrependV4(lh.myVpnIp, to)
|
|
|
+ case addrPort.Addr().Is6():
|
|
|
+ to := NewIp6AndPortFromNetIP(addrPort.Addr(), addrPort.Port())
|
|
|
+ if !lh.unlockedShouldAddV6(vpnIp, to) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ am.unlockedPrependV6(lh.myVpnIp, to)
|
|
|
}
|
|
|
- am.unlockedPrependV6(lh.myVpnIp, to)
|
|
|
}
|
|
|
|
|
|
// Mark it as static in the caller provided map
|
|
|
staticList[vpnIp] = struct{}{}
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// addCalculatedRemotes adds any calculated remotes based on the
|
|
@@ -545,12 +612,42 @@ func (lh *LightHouse) addCalculatedRemotes(vpnIp iputil.VpnIp) bool {
|
|
|
func (lh *LightHouse) unlockedGetRemoteList(vpnIp iputil.VpnIp) *RemoteList {
|
|
|
am, ok := lh.addrMap[vpnIp]
|
|
|
if !ok {
|
|
|
- am = NewRemoteList()
|
|
|
+ am = NewRemoteList(func(a netip.Addr) bool { return lh.shouldAdd(vpnIp, a) })
|
|
|
lh.addrMap[vpnIp] = am
|
|
|
}
|
|
|
return am
|
|
|
}
|
|
|
|
|
|
+func (lh *LightHouse) shouldAdd(vpnIp iputil.VpnIp, to netip.Addr) bool {
|
|
|
+ switch {
|
|
|
+ case to.Is4():
|
|
|
+ ipBytes := to.As4()
|
|
|
+ ip := iputil.Ip2VpnIp(ipBytes[:])
|
|
|
+ allow := lh.GetRemoteAllowList().AllowIpV4(vpnIp, ip)
|
|
|
+ if lh.l.Level >= logrus.TraceLevel {
|
|
|
+ lh.l.WithField("remoteIp", vpnIp).WithField("allow", allow).Trace("remoteAllowList.Allow")
|
|
|
+ }
|
|
|
+ if !allow || ipMaskContains(lh.myVpnIp, lh.myVpnZeros, ip) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ case to.Is6():
|
|
|
+ ipBytes := to.As16()
|
|
|
+
|
|
|
+ hi := binary.BigEndian.Uint64(ipBytes[:8])
|
|
|
+ lo := binary.BigEndian.Uint64(ipBytes[8:])
|
|
|
+ allow := lh.GetRemoteAllowList().AllowIpV6(vpnIp, hi, lo)
|
|
|
+ if lh.l.Level >= logrus.TraceLevel {
|
|
|
+ lh.l.WithField("remoteIp", to).WithField("allow", allow).Trace("remoteAllowList.Allow")
|
|
|
+ }
|
|
|
+
|
|
|
+ // We don't check our vpn network here because nebula does not support ipv6 on the inside
|
|
|
+ if !allow {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
// unlockedShouldAddV4 checks if to is allowed by our allow list
|
|
|
func (lh *LightHouse) unlockedShouldAddV4(vpnIp iputil.VpnIp, to *Ip4AndPort) bool {
|
|
|
allow := lh.GetRemoteAllowList().AllowIpV4(vpnIp, iputil.VpnIp(to.Ip))
|
|
@@ -609,6 +706,14 @@ func NewIp4AndPort(ip net.IP, port uint32) *Ip4AndPort {
|
|
|
return &ipp
|
|
|
}
|
|
|
|
|
|
+func NewIp4AndPortFromNetIP(ip netip.Addr, port uint16) *Ip4AndPort {
|
|
|
+ v4Addr := ip.As4()
|
|
|
+ return &Ip4AndPort{
|
|
|
+ Ip: binary.BigEndian.Uint32(v4Addr[:]),
|
|
|
+ Port: uint32(port),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func NewIp6AndPort(ip net.IP, port uint32) *Ip6AndPort {
|
|
|
return &Ip6AndPort{
|
|
|
Hi: binary.BigEndian.Uint64(ip[:8]),
|
|
@@ -617,6 +722,14 @@ func NewIp6AndPort(ip net.IP, port uint32) *Ip6AndPort {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func NewIp6AndPortFromNetIP(ip netip.Addr, port uint16) *Ip6AndPort {
|
|
|
+ ip6Addr := ip.As16()
|
|
|
+ return &Ip6AndPort{
|
|
|
+ Hi: binary.BigEndian.Uint64(ip6Addr[:8]),
|
|
|
+ Lo: binary.BigEndian.Uint64(ip6Addr[8:]),
|
|
|
+ Port: uint32(port),
|
|
|
+ }
|
|
|
+}
|
|
|
func NewUDPAddrFromLH4(ipp *Ip4AndPort) *udp.Addr {
|
|
|
ip := ipp.Ip
|
|
|
return udp.NewAddr(
|