[-- Attachment #1: Type: text/plain, Size: 2647 bytes --] Hi, I know of the reresolvedns script for Linux but there is no nice solution for the other platforms. I think this is a feature many users would benefit from if directly integrated into the wireguard apps (including me). I implemented a new config option which periodically updates the endpoint IP in the same way the reresolvedns script does it, but inside the windows app. A go routine gets created inside the tunnel manager for each peer which has the option set. if the option is not set for any peer there is no change in behavior. Kudos to the well-crafted build system and awesome build.bat for windows, this made it very easy to get started. I think there are some points left open: - The name of the option cannot easily be changed later as it would require additional migration code (I’m not tied to the name “Update endpoint IP”). - Is the amount of logging this feature currently creates ok? - I haven’t updated the localization to not blow this patch up to much, this can be done after settling on a name for the option. - Most of the code is ui related and pretty straight forward. I implemented the PeerUpdateEndpointConfiguration based on my analysis of the wireguard-tools set.c source and it seems to work but I think someone with a deeper understanding of the driver interface should check that. During my testing the feature seems to work fine with 1 peer, maybe someone can test this in a more complicated setup. - I think this will not work if all traffic is routed through the VPN including DNS therefor once the IP of the peer changes, the link breaks and updating the endpoint IP will not work. I’m not sure if there is a way to fix this. - I’m not sure on the position of config options only being supported on specific clients. I think this is already the case but I don’t have a good overview. It would be nice to also have this feature in the other clients like iOS/macOS and android. I think it is not necessary to update the Linux tool. If desired I think it could be included in wg-quick. I’m unsure how to tackle this feature for macOS/iOS, my assumption is that it would need to be included in the PacketTunnelProvider and be part of the NetworkExtension or maybe in the userland driver wireguard-go? Sadly, due to all the apple signing and provisioning hoops you have to jump through I was not able to get even the macOS app to build using my free developer account. I hope this patch can be reviewed and upstreamed. The patch can also be found here: https://github.com/WireGuard/wireguard-windows/pull/18 Best regards - Tobias Tangemann [-- Attachment #2: 0001-tunnel-conf-ui-periodic-update-of-endpoint-ip.patch --] [-- Type: application/octet-stream, Size: 12617 bytes --] From 7ce8acd6c3eeb544452b77027b5575f998fc0bd8 Mon Sep 17 00:00:00 2001 From: Tobias Tangemann <tobias@tangemann.org> 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 e6cc6aac..1c91ee29 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 b09f4603..fcd27153 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 477a5205..e821e9d5 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 61abb672..7feb536a 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 374d71d3..3ab52932 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 3d16f38f..9a0b7922 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 47946093..254a2ae2 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 42f6e7b7..f8447f1e 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) { -- 2.34.1.windows.1