From 7ce8acd6c3eeb544452b77027b5575f998fc0bd8 Mon Sep 17 00:00:00 2001 From: Tobias Tangemann Date: Tue, 7 Dec 2021 21:17:50 +0100 Subject: [PATCH] tunnel+conf+ui: periodic update of endpoint ip --- conf/config.go | 2 ++ conf/dnsresolver_windows.go | 7 +++++-- conf/parser.go | 26 ++++++++++++++++++++++++++ conf/writer.go | 25 +++++++++++++++++++++++++ tunnel/service.go | 33 +++++++++++++++++++++++++++++++++ ui/confview.go | 8 ++++++++ ui/syntax/highlighter.go | 13 +++++++++++++ ui/syntax/syntaxedit.go | 33 +++++++++++++++++---------------- 8 files changed, 129 insertions(+), 18 deletions(-) diff --git a/conf/config.go b/conf/config.go index e6cc6aacb..1c91ee294 100644 --- a/conf/config.go +++ b/conf/config.go @@ -57,10 +57,12 @@ type Peer struct { AllowedIPs []netip.Prefix Endpoint Endpoint PersistentKeepalive uint16 + UpdateEndpointIP uint16 RxBytes Bytes TxBytes Bytes LastHandshakeTime HandshakeTime + UnresolvedHost string } func (conf *Config) IntersectsWith(other *Config) bool { diff --git a/conf/dnsresolver_windows.go b/conf/dnsresolver_windows.go index b09f4603f..fcd271534 100644 --- a/conf/dnsresolver_windows.go +++ b/conf/dnsresolver_windows.go @@ -19,7 +19,7 @@ import ( //sys internetGetConnectedState(flags *uint32, reserved uint32) (connected bool) = wininet.InternetGetConnectedState -func resolveHostname(name string) (resolvedIPString string, err error) { +func ResolveHostname(name string) (resolvedIPString string, err error) { maxTries := 10 if services.StartedAtBoot() { maxTries *= 4 @@ -90,8 +90,11 @@ func (config *Config) ResolveEndpoints() error { if config.Peers[i].Endpoint.IsEmpty() { continue } + if config.Peers[i].UpdateEndpointIP > 0 { + config.Peers[i].UnresolvedHost = config.Peers[i].Endpoint.Host + } var err error - config.Peers[i].Endpoint.Host, err = resolveHostname(config.Peers[i].Endpoint.Host) + config.Peers[i].Endpoint.Host, err = ResolveHostname(config.Peers[i].Endpoint.Host) if err != nil { return err } diff --git a/conf/parser.go b/conf/parser.go index 477a52057..e821e9d58 100644 --- a/conf/parser.go +++ b/conf/parser.go @@ -109,6 +109,20 @@ func parsePersistentKeepalive(s string) (uint16, error) { return uint16(m), nil } +func parseUpdateEndpointIP(s string) (uint16, error) { + if s == "off" { + return 0, nil + } + m, err := strconv.Atoi(s) + if err != nil { + return 0, err + } + if m < 0 || m > 65535 { + return 0, &ParseError{l18n.Sprintf("Invalid update endpoint IP"), s} + } + return uint16(m), nil +} + func parseTableOff(s string) (bool, error) { if s == "off" { return true, nil @@ -294,6 +308,12 @@ func FromWgQuick(s string, name string) (*Config, error) { return nil, err } peer.PersistentKeepalive = p + case "updateendpointip": + p, err := parseUpdateEndpointIP(val) + if err != nil { + return nil, err + } + peer.UpdateEndpointIP = p case "endpoint": e, err := parseEndpoint(val) if err != nil { @@ -378,6 +398,12 @@ func FromDriverConfiguration(interfaze *driver.Interface, existingConfig *Config if p.Flags&driver.PeerHasPersistentKeepalive != 0 { peer.PersistentKeepalive = p.PersistentKeepalive } + for i := range existingConfig.Peers { + if existingConfig.Peers[i].PublicKey == peer.PublicKey && existingConfig.Peers[i].UpdateEndpointIP > 0 { + // Get UpdateEndpointIP option from config as it is cannot be retrieved from the driver + peer.UpdateEndpointIP = existingConfig.Peers[i].UpdateEndpointIP + } + } peer.TxBytes = Bytes(p.TxBytes) peer.RxBytes = Bytes(p.RxBytes) if p.LastHandshake != 0 { diff --git a/conf/writer.go b/conf/writer.go index 61abb672c..7feb536a8 100644 --- a/conf/writer.go +++ b/conf/writer.go @@ -88,6 +88,10 @@ func (conf *Config) ToWgQuick() string { if peer.PersistentKeepalive > 0 { output.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", peer.PersistentKeepalive)) } + + if peer.UpdateEndpointIP > 0 { + output.WriteString(fmt.Sprintf("UpdateEndpointIP = %d\n", peer.UpdateEndpointIP)) + } } return output.String() } @@ -139,3 +143,24 @@ func (config *Config) ToDriverConfiguration() (*driver.Interface, uint32) { } return c.Interface() } + +func PeerUpdateEndpointConfiguration(peer *Peer, newIP string) (*driver.Interface, uint32) { + preallocation := unsafe.Sizeof(driver.Interface{}) + uintptr(1)*unsafe.Sizeof(driver.Peer{}) + var c driver.ConfigBuilder + c.Preallocate(uint32(preallocation)) + c.AppendInterface(&driver.Interface{ + PeerCount: uint32(1), + }) + + var endpoint winipcfg.RawSockaddrInet + addr, err := netip.ParseAddr(newIP) + if err == nil { + endpoint.SetAddrPort(netip.AddrPortFrom(addr, peer.Endpoint.Port)) + } + c.AppendPeer(&driver.Peer{ + Flags: driver.PeerHasEndpoint | driver.PeerHasPublicKey, + PublicKey: peer.PublicKey, + Endpoint: endpoint, + }) + return c.Interface() +} diff --git a/tunnel/service.go b/tunnel/service.go index 374d71d3e..3ab52932e 100644 --- a/tunnel/service.go +++ b/tunnel/service.go @@ -219,6 +219,8 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, return } + startUpdateEndpointIP(adapter, config) + changes <- svc.Status{State: serviceState, Accepts: svc.AcceptStop | svc.AcceptShutdown} var started bool @@ -247,6 +249,37 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, } } +func startUpdateEndpointIP(adapter *driver.Adapter, config *conf.Config) { + for i := range config.Peers { + if !config.Peers[i].Endpoint.IsEmpty() && config.Peers[i].UpdateEndpointIP > 0 { + go resolveDNSLoop(adapter, &config.Peers[i], i+1) + } + } +} + +func resolveDNSLoop(adapter *driver.Adapter, peer *conf.Peer, peerNumber int) { + for { + time.Sleep(time.Second * time.Duration(peer.UpdateEndpointIP)) + + log.Printf("Checking DNS of endpoint for peer %d", peerNumber) + resolvedIPString, err := conf.ResolveHostname(peer.UnresolvedHost) + if err != nil { + log.Printf("Error resolving hostname of peer %d", peerNumber) + continue + } + + if resolvedIPString != peer.Endpoint.Host { + peer.Endpoint.Host = resolvedIPString + err = adapter.SetConfiguration(conf.PeerUpdateEndpointConfiguration(peer, resolvedIPString)) + if err == nil { + log.Printf("IP for peer %d updated (%v)", peerNumber, resolvedIPString) + } else { + log.Printf("Error updating IP for peer %d: %v", peerNumber, err) + } + } + } +} + func Run(confPath string) error { name, err := conf.NameFromPath(confPath) if err != nil { diff --git a/ui/confview.go b/ui/confview.go index 3d16f38f7..9a0b7922b 100644 --- a/ui/confview.go +++ b/ui/confview.go @@ -62,6 +62,7 @@ type peerView struct { allowedIPs *labelTextLine endpoint *labelTextLine persistentKeepalive *labelTextLine + updateEndpointIP *labelTextLine latestHandshake *labelTextLine transfer *labelTextLine lines []widgetsLine @@ -337,6 +338,7 @@ func newPeerView(parent walk.Container) (*peerView, error) { {l18n.Sprintf("Allowed IPs:"), &pv.allowedIPs}, {l18n.Sprintf("Endpoint:"), &pv.endpoint}, {l18n.Sprintf("Persistent keepalive:"), &pv.persistentKeepalive}, + {l18n.Sprintf("Update endpoint IP:"), &pv.updateEndpointIP}, {l18n.Sprintf("Latest handshake:"), &pv.latestHandshake}, {l18n.Sprintf("Transfer:"), &pv.transfer}, } @@ -476,6 +478,12 @@ func (pv *peerView) apply(c *conf.Peer) { pv.persistentKeepalive.hide() } + if c.UpdateEndpointIP > 0 { + pv.updateEndpointIP.show(strconv.Itoa(int(c.UpdateEndpointIP))) + } else { + pv.updateEndpointIP.hide() + } + if !c.LastHandshakeTime.IsEmpty() { pv.latestHandshake.show(c.LastHandshakeTime.String()) } else { diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go index 47946093f..254a2ae22 100644 --- a/ui/syntax/highlighter.go +++ b/ui/syntax/highlighter.go @@ -24,6 +24,7 @@ const ( highlightPort highlightMTU highlightKeepalive + highlightUpdateEndpointIP highlightComment highlightDelimiter highlightTable @@ -268,6 +269,13 @@ func (s stringSpan) isValidPersistentKeepAlive() bool { return s.isValidUint(false, 0, 65535) } +func (s stringSpan) isValidUpdateEndpointIP() bool { + if s.isSame("off") { + return true + } + return s.isValidUint(false, 0, 65535) +} + // It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length. func (s stringSpan) isValidPrePostUpDown() bool { return s.len != 0 @@ -376,6 +384,7 @@ const ( fieldAllowedIPs fieldEndpoint fieldPersistentKeepalive + fieldUpdateEndpointIP fieldInvalid ) @@ -413,6 +422,8 @@ func (s stringSpan) field() field { return fieldEndpoint case s.isCaselessSame("PersistentKeepalive"): return fieldPersistentKeepalive + case s.isCaselessSame("UpdateEndpointIP"): + return fieldUpdateEndpointIP case s.isCaselessSame("PreUp"): return fieldPreUp case s.isCaselessSame("PostUp"): @@ -524,6 +535,8 @@ func (hsa *highlightSpanArray) highlightValue(parent stringSpan, s stringSpan, s hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort)) case fieldPersistentKeepalive: hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive)) + case fieldUpdateEndpointIP: + hsa.append(parent.s, s, validateHighlight(s.isValidUpdateEndpointIP(), highlightUpdateEndpointIP)) case fieldEndpoint: if !s.isValidEndpoint() { hsa.append(parent.s, s, highlightError) diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go index 42f6e7b79..f8447f1e6 100644 --- a/ui/syntax/syntaxedit.go +++ b/ui/syntax/syntaxedit.go @@ -90,22 +90,23 @@ type spanStyle struct { } var stylemap = map[highlight]spanStyle{ - highlightSection: spanStyle{color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD}, - highlightField: spanStyle{color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD}, - highlightPrivateKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, - highlightPublicKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, - highlightPresharedKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, - highlightIP: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, - highlightCidr: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, - highlightHost: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, - highlightPort: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, - highlightMTU: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, - highlightTable: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, - highlightKeepalive: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, - highlightComment: spanStyle{color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC}, - highlightDelimiter: spanStyle{color: win.RGB(0x00, 0x00, 0x00)}, - highlightCmd: spanStyle{color: win.RGB(0x63, 0x75, 0x89)}, - highlightError: spanStyle{color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE}, + highlightSection: spanStyle{color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD}, + highlightField: spanStyle{color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD}, + highlightPrivateKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightPublicKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightPresharedKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightIP: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, + highlightCidr: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, + highlightHost: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, + highlightPort: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, + highlightMTU: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightTable: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightKeepalive: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightUpdateEndpointIP: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightComment: spanStyle{color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC}, + highlightDelimiter: spanStyle{color: win.RGB(0x00, 0x00, 0x00)}, + highlightCmd: spanStyle{color: win.RGB(0x63, 0x75, 0x89)}, + highlightError: spanStyle{color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE}, } func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {