From b6234abfb3dde8cbc3fff53901ef28286369b614 Mon Sep 17 00:00:00 2001 From: Nathan Brown Date: Mon, 1 Mar 2021 19:06:01 -0600 Subject: [PATCH] Add a way to trigger punch backs via lighthouse (#394) --- control.go | 6 ++++ hostmap.go | 5 ++++ inside.go | 12 ++++++++ interface.go | 5 +++- lighthouse.go | 81 +++++++++++++++++++++++---------------------------- main.go | 2 +- 6 files changed, 65 insertions(+), 46 deletions(-) diff --git a/control.go b/control.go index e16d07d..8e7eb0c 100644 --- a/control.go +++ b/control.go @@ -65,6 +65,12 @@ func (c *Control) ShutdownBlock() { // RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change func (c *Control) RebindUDPServer() { _ = c.f.outside.Rebind() + + // Trigger a lighthouse update, useful for mobile clients that should have an update interval of 0 + c.f.lightHouse.SendUpdate(c.f) + + // Let the main interface know that we rebound so that underlying tunnels know to trigger punches from their remotes + c.f.rebindCount++ } // ListHostmap returns details about the actual or pending (handshaking) hostmap diff --git a/hostmap.go b/hostmap.go index 352b116..391cdfb 100644 --- a/hostmap.go +++ b/hostmap.go @@ -51,6 +51,11 @@ type HostInfo struct { recvError int remoteCidr *CIDRTree + // lastRebindCount is the other side of Interface.rebindCount, if these values don't match then we need to ask LH + // for a punch from the remote end of this tunnel. The goal being to prime their conntrack for our traffic just like + // with a handshake + lastRebindCount int8 + lastRoam time.Time lastRoamRemote *udpAddr } diff --git a/inside.go b/inside.go index 302b22b..921fae6 100644 --- a/inside.go +++ b/inside.go @@ -229,6 +229,18 @@ func (f *Interface) sendNoMetrics(t NebulaMessageType, st NebulaMessageSubType, out = HeaderEncode(out, Version, uint8(t), uint8(st), hostinfo.remoteIndexId, c) f.connectionManager.Out(hostinfo.hostId) + // Query our LH if we haven't since the last time we've been rebound, this will cause the remote to punch against + // all our IPs and enable a faster roaming. + if hostinfo.lastRebindCount != f.rebindCount { + //NOTE: there is an update hole if a tunnel isn't used and exactly 256 rebinds occur before the tunnel is + // finally used again. This tunnel would eventually be torn down and recreated if this action didn't help. + f.lightHouse.Query(hostinfo.hostId, f) + hostinfo.lastRebindCount = f.rebindCount + if l.Level >= logrus.DebugLevel { + l.WithField("vpnIp", hostinfo.hostId).Debug("Lighthouse update triggered for punch due to rebind counter") + } + } + out, err = ci.eKey.EncryptDanger(out, out, p, c, nb) //TODO: see above note on lock //ci.writeLock.Unlock() diff --git a/interface.go b/interface.go index d17f6a8..377dde0 100644 --- a/interface.go +++ b/interface.go @@ -61,7 +61,10 @@ type Interface struct { dropMulticast bool udpBatchSize int routines int - version string + + // rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse + rebindCount int8 + version string conntrackCacheTimeout time.Duration diff --git a/lighthouse.go b/lighthouse.go index aaba930..59bc0ba 100644 --- a/lighthouse.go +++ b/lighthouse.go @@ -41,7 +41,7 @@ type LightHouse struct { staticList map[uint32]struct{} lighthouses map[uint32]struct{} interval int - nebulaPort int + nebulaPort uint32 punchBack bool punchDelay time.Duration @@ -54,7 +54,7 @@ type EncWriter interface { SendMessageToAll(t NebulaMessageType, st NebulaMessageSubType, vpnIp uint32, p, nb, out []byte) } -func NewLightHouse(amLighthouse bool, myIp uint32, ips []uint32, interval int, nebulaPort int, pc *udpConn, punchBack bool, punchDelay time.Duration, metricsEnabled bool) *LightHouse { +func NewLightHouse(amLighthouse bool, myIp uint32, ips []uint32, interval int, nebulaPort uint32, pc *udpConn, punchBack bool, punchDelay time.Duration, metricsEnabled bool) *LightHouse { h := LightHouse{ amLighthouse: amLighthouse, myIp: myIp, @@ -208,12 +208,6 @@ func (lh *LightHouse) IsLighthouseIP(vpnIP uint32) bool { return false } -// Quick generators for protobuf - -func NewLhQueryByIpString(VpnIp string) *NebulaMeta { - return NewLhQueryByInt(ip2int(net.ParseIP(VpnIp))) -} - func NewLhQueryByInt(VpnIp uint32) *NebulaMeta { return &NebulaMeta{ Type: NebulaMeta_HostQuery, @@ -223,15 +217,10 @@ func NewLhQueryByInt(VpnIp uint32) *NebulaMeta { } } -func NewLhWhoami() *NebulaMeta { - return &NebulaMeta{ - Type: NebulaMeta_HostWhoami, - Details: &NebulaMetaDetails{}, - } +func NewIpAndPort(ip net.IP, port uint32) IpAndPort { + return IpAndPort{Ip: ip2int(ip), Port: port} } -// End Quick generators for protobuf - func NewIpAndPortFromUDPAddr(addr udpAddr) IpAndPort { return IpAndPort{Ip: udp2ipInt(&addr), Port: uint32(addr.Port)} } @@ -242,39 +231,43 @@ func (lh *LightHouse) LhUpdateWorker(f EncWriter) { } for { - ipp := []*IpAndPort{} - - for _, e := range *localIps(lh.localAllowList) { - // Only add IPs that aren't my VPN/tun IP - if ip2int(e) != lh.myIp { - ipp = append(ipp, &IpAndPort{Ip: ip2int(e), Port: uint32(lh.nebulaPort)}) - //fmt.Println(e) - } - } - m := &NebulaMeta{ - Type: NebulaMeta_HostUpdateNotification, - Details: &NebulaMetaDetails{ - VpnIp: lh.myIp, - IpAndPorts: ipp, - }, - } - - lh.metricTx(NebulaMeta_HostUpdateNotification, int64(len(lh.lighthouses))) - nb := make([]byte, 12, 12) - out := make([]byte, mtu) - for vpnIp := range lh.lighthouses { - mm, err := proto.Marshal(m) - if err != nil { - l.Debugf("Invalid marshal to update") - } - //l.Error("LIGHTHOUSE PACKET SEND", mm) - f.SendMessageToVpnIp(lightHouse, 0, vpnIp, mm, nb, out) - - } + lh.SendUpdate(f) time.Sleep(time.Second * time.Duration(lh.interval)) } } +func (lh *LightHouse) SendUpdate(f EncWriter) { + var ipps []*IpAndPort + + for _, e := range *localIps(lh.localAllowList) { + // Only add IPs that aren't my VPN/tun IP + if ip2int(e) != lh.myIp { + ipp := NewIpAndPort(e, lh.nebulaPort) + ipps = append(ipps, &ipp) + } + } + m := &NebulaMeta{ + Type: NebulaMeta_HostUpdateNotification, + Details: &NebulaMetaDetails{ + VpnIp: lh.myIp, + IpAndPorts: ipps, + }, + } + + lh.metricTx(NebulaMeta_HostUpdateNotification, int64(len(lh.lighthouses))) + nb := make([]byte, 12, 12) + out := make([]byte, mtu) + for vpnIp := range lh.lighthouses { + mm, err := proto.Marshal(m) + if err != nil { + l.Debugf("Invalid marshal to update") + } + //l.Error("LIGHTHOUSE PACKET SEND", mm) + f.SendMessageToVpnIp(lightHouse, 0, vpnIp, mm, nb, out) + + } +} + type LightHouseHandler struct { lh *LightHouse nb []byte diff --git a/main.go b/main.go index 2f81fac..b76bbe7 100644 --- a/main.go +++ b/main.go @@ -268,7 +268,7 @@ func Main(config *Config, configTest bool, buildVersion string, logger *logrus.L lighthouseHosts, //TODO: change to a duration config.GetInt("lighthouse.interval", 10), - port, + uint32(port), udpConns[0], punchy.Respond, punchy.Delay,