@@ -2,6 +2,7 @@ use std::{
22 error:: Error ,
33 fs:: { File , read_to_string} ,
44 io:: Write ,
5+ net:: IpAddr ,
56 path:: Path ,
67 str:: FromStr ,
78 time:: Duration ,
@@ -11,9 +12,11 @@ use base64::Engine;
1112use defguard_wireguard_rs:: {
1213 InterfaceConfiguration , WGApi , WireguardInterfaceApi , host:: Peer , key:: Key , net:: IpAddrMask ,
1314} ;
15+ use hickory_resolver:: Resolver ;
1416use openssl:: symm:: { Cipher , Crypter , Mode } ;
1517use serde:: { Deserialize , Serialize } ;
1618use tokio:: time:: sleep;
19+ use url:: Url ;
1720use x25519_dalek:: { PublicKey , StaticSecret } ;
1821
1922#[ cfg( not( any( target_os = "macos" , target_os = "windows" , target_arch = "arm" ) ) ) ]
@@ -32,6 +35,7 @@ struct ServerConfig {
3235 pub pubkey : String ,
3336 pub vpc_id : Option < String > ,
3437 pub vpc_ip : Option < String > ,
38+ pub persistent_keepalive_interval : Option < u16 > ,
3539}
3640
3741#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -67,6 +71,31 @@ fn load_key() -> (PublicKey, StaticSecret) {
6771 ( pubkey, secret_key)
6872}
6973
74+ async fn fetch_config_content ( config_url : & str ) -> Result < String , Box < dyn Error > > {
75+ // Try to parse as URL first
76+ if let Ok ( url) = Url :: parse ( config_url) {
77+ match url. scheme ( ) {
78+ "file" => {
79+ // Handle file:// URLs
80+ let path = url. to_file_path ( ) . map_err ( |_| {
81+ format ! ( "Invalid file URL: {}" , config_url)
82+ } ) ?;
83+ Ok ( read_to_string ( path) ?)
84+ }
85+ "http" | "https" => {
86+ // Handle HTTP(S) URLs
87+ Ok ( reqwest:: get ( config_url) . await ?. text ( ) . await ?)
88+ }
89+ scheme => {
90+ Err ( format ! ( "Unsupported URL scheme: {}" , scheme) . into ( ) )
91+ }
92+ }
93+ } else {
94+ // If URL parsing fails, treat it as a plain file path
95+ Ok ( read_to_string ( config_url) ?)
96+ }
97+ }
98+
7099fn decrypt_config ( encrypted : & str , secret : & str ) -> Result < String , Box < dyn Error > > {
71100 // Step 1: Base64 decode
72101 let data = base64:: engine:: general_purpose:: STANDARD
@@ -107,13 +136,40 @@ fn decrypt_config(encrypted: &str, secret: &str) -> Result<String, Box<dyn Error
107136 Ok ( String :: from_utf8 ( plaintext) ?)
108137}
109138
139+ async fn resolve_to_ip ( host : & str ) -> Option < String > {
140+ // Try to parse as IP address first
141+ if let Ok ( _) = host. parse :: < IpAddr > ( ) {
142+ return Some ( host. to_string ( ) ) ;
143+ }
144+
145+ // If not an IP, try to resolve as domain name
146+ let resolver = match Resolver :: builder_tokio ( ) {
147+ Ok ( builder) => builder. build ( ) ,
148+ Err ( e) => {
149+ println ! ( "Failed to create DNS resolver: {}" , e) ;
150+ return None ;
151+ }
152+ } ;
153+
154+ match resolver. lookup_ip ( host) . await {
155+ Ok ( response) => {
156+ // Get the first IP address from the response
157+ response. iter ( ) . next ( ) . map ( |ip| ip. to_string ( ) )
158+ }
159+ Err ( e) => {
160+ println ! ( "Failed to resolve domain {}: {}" , host, e) ;
161+ None
162+ }
163+ }
164+ }
165+
110166async fn fetch_and_apply_config (
111167 wgapi : & WGApi ,
112168 interface_config : & mut InterfaceConfiguration ,
113169 pubkey : & str ,
114170 config : & DaemonConfig ,
115171) -> Result < ( ) , Box < dyn Error > > {
116- let mut r = reqwest :: get ( & config. config_url ) . await ? . text ( ) . await ?;
172+ let mut r = fetch_config_content ( & config. config_url ) . await ?;
117173 if config. secret . is_some ( ) {
118174 r = decrypt_config (
119175 & r. replace ( "\n " , "" ) . replace ( " " , "" ) ,
@@ -161,19 +217,36 @@ async fn fetch_and_apply_config(
161217 let mut should_reconfigure = false ;
162218 let mut peer_cidr = IpAddrMask :: from_str ( & peer. internal_cidr ) ?;
163219 peer_cidr. cidr = 32 ;
164- let peer_endpoint_ip = if my_config. vpc_id . is_some ( )
220+ let peer_endpoint_host = if my_config. vpc_id . is_some ( )
165221 && my_config. vpc_id == peer. vpc_id
166222 && peer. vpc_ip . is_some ( )
167223 {
168224 peer. vpc_ip . as_ref ( ) . unwrap ( ) . clone ( )
169225 } else {
170226 peer. ip . clone ( )
171227 } ;
228+
229+ // Resolve domain name to IP address
230+ let peer_endpoint_ip = resolve_to_ip ( & peer_endpoint_host) . await ;
231+
232+ // Calculate keepalive interval as minimum of both endpoints
233+ let keepalive_interval = match ( my_config. persistent_keepalive_interval , peer. persistent_keepalive_interval ) {
234+ ( Some ( a) , Some ( b) ) => Some ( a. min ( b) ) ,
235+ ( Some ( a) , None ) => Some ( a) ,
236+ ( None , Some ( b) ) => Some ( b) ,
237+ ( None , None ) => None ,
238+ } ;
239+
172240 if let Some ( p) = current_peer {
173241 if let Some ( endpoint) = p. endpoint {
174- if endpoint. ip ( ) . to_string ( ) != peer_endpoint_ip
175- || endpoint. port ( ) as u32 != peer. port
176- {
242+ if let Some ( resolved_ip) = & peer_endpoint_ip {
243+ if endpoint. ip ( ) . to_string ( ) != * resolved_ip
244+ || endpoint. port ( ) as u32 != peer. port
245+ {
246+ should_reconfigure = true ;
247+ }
248+ } else {
249+ // Resolution failed, need to reconfigure to clear endpoint
177250 should_reconfigure = true ;
178251 }
179252 } else {
@@ -184,24 +257,48 @@ async fn fetch_and_apply_config(
184257 } else if p. allowed_ips [ 0 ] != peer_cidr {
185258 should_reconfigure = true ;
186259 }
260+ if p. persistent_keepalive_interval != keepalive_interval {
261+ should_reconfigure = true ;
262+ }
187263 } else {
188264 should_reconfigure = true ;
189265 }
190266 if should_reconfigure {
267+ let endpoint = match & peer_endpoint_ip {
268+ Some ( ip) => {
269+ // Check if IP is IPv6 and needs brackets for SocketAddr parsing
270+ let endpoint_str = match ip. parse :: < IpAddr > ( ) {
271+ Ok ( IpAddr :: V6 ( _) ) => format ! ( "[{}]:{}" , ip, peer. port) ,
272+ _ => format ! ( "{}:{}" , ip, peer. port) ,
273+ } ;
274+ match endpoint_str. parse ( ) {
275+ Ok ( addr) => Some ( addr) ,
276+ Err ( e) => {
277+ println ! ( "Failed to parse endpoint {}: {}" , endpoint_str, e) ;
278+ None
279+ }
280+ }
281+ }
282+ None => None ,
283+ } ;
191284 let wg_peer = Peer {
192285 public_key : pubkey,
193286 preshared_key : None ,
194287 protocol_version : None ,
195- endpoint : Some ( format ! ( "{}:{}" , peer_endpoint_ip , peer . port ) . parse ( ) ? ) ,
288+ endpoint,
196289 last_handshake : None ,
197290 tx_bytes : 0 ,
198291 rx_bytes : 0 ,
199- persistent_keepalive_interval : None ,
292+ persistent_keepalive_interval : keepalive_interval ,
200293 allowed_ips : vec ! [ peer_cidr] ,
201294 } ;
295+ let endpoint_display = peer_endpoint_ip
296+ . as_ref ( )
297+ . map ( |ip| format ! ( "{}:{}" , ip, peer. port) )
298+ . unwrap_or_else ( || "(no endpoint - resolution failed)" . to_string ( ) ) ;
202299 println ! (
203300 "Configuring peer: {} {} {}" ,
204- & peer. pubkey, & peer_endpoint_ip , & peer. internal_cidr
301+ & peer. pubkey, endpoint_display , & peer. internal_cidr
205302 ) ;
206303 wgapi. configure_peer ( & wg_peer) ?;
207304 }
0 commit comments